mirror of
https://github.com/Threnklyn/jira.git
synced 2026-05-30 01:38:28 +02:00
Compare commits
28 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| adc08935b4 | |||
| 073c8a3694 | |||
| c4a31a498e | |||
| bcad37089a | |||
| b2ba8de15d | |||
| 6016bda571 | |||
| 34ca09cf1a | |||
| d7fb88ee41 | |||
| de4fe76fec | |||
| 5b870cb7a2 | |||
| 89bb82b3f2 | |||
| dd0f5efd32 | |||
| 68b5e60dd9 | |||
| 71acc5d7fc | |||
| 4f91cecf25 | |||
| 688b987895 | |||
| 71bb04fabb | |||
| 3a9f763f9d | |||
| d86d85f7b2 | |||
| 4b798cbfb4 | |||
| 598924b51d | |||
| 674957af5d | |||
| c568d7e921 | |||
| 6eb3567ca5 | |||
| 87ec73c5c3 | |||
| 23551abb11 | |||
| 693e1441f7 | |||
| e58625b00c |
@@ -5,3 +5,5 @@ src/github.com/docopt/
|
|||||||
src/github.com/mgutz/
|
src/github.com/mgutz/
|
||||||
src/github.com/op/
|
src/github.com/op/
|
||||||
src/gopkg.in/
|
src/gopkg.in/
|
||||||
|
jira
|
||||||
|
jira.exe
|
||||||
@@ -1,5 +1,22 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## 0.1.3 - 2016-07-30
|
||||||
|
|
||||||
|
* [[#43](https://github.com/Netflix-Skunkworks/go-jira/issues/43)] add support for jira done|todo|prog commands [Cory Bennett] [[dd7d1cc](https://github.com/Netflix-Skunkworks/go-jira/commit/dd7d1cc)]
|
||||||
|
* Reporter is not generally editable. [Mike Pountney] [[a637b43](https://github.com/Netflix-Skunkworks/go-jira/commit/a637b43)]
|
||||||
|
|
||||||
|
## 0.1.2 - 2016-06-29
|
||||||
|
|
||||||
|
* [[#44](https://github.com/Netflix-Skunkworks/go-jira/issues/44)] Close tmpfile before rename to work around "The process cannot access the file because it is being used by another process" error on windows. [Cory Bennett] [[0980f8e](https://github.com/Netflix-Skunkworks/go-jira/commit/0980f8e)]
|
||||||
|
|
||||||
|
## 0.1.1 - 2016-06-28
|
||||||
|
|
||||||
|
* use USERPROFILE instead of HOME for windows, rework paths to use filepath.Join for better cross platform support [Cory Bennett] [[adcedc4](https://github.com/Netflix-Skunkworks/go-jira/commit/adcedc4)]
|
||||||
|
* Include templates from a system path [Mike Pountney] [[cf10f53](https://github.com/Netflix-Skunkworks/go-jira/commit/cf10f53)]
|
||||||
|
* Added support for the ```expand``` option for Issues [tobyjoe] [[fb4afc9](https://github.com/Netflix-Skunkworks/go-jira/commit/fb4afc9)]
|
||||||
|
* change for api changes to go-logging [Cory Bennett] [[7bfc6e8](https://github.com/Netflix-Skunkworks/go-jira/commit/7bfc6e8)]
|
||||||
|
* Fix issuetype calls adding URL escaping [Jonathan Wright] [[e4a25e2](https://github.com/Netflix-Skunkworks/go-jira/commit/e4a25e2)]
|
||||||
|
|
||||||
## 0.1.0 - 2016-01-29
|
## 0.1.0 - 2016-01-29
|
||||||
|
|
||||||
* Fixes [#32](https://github.com/Netflix-Skunkworks/go-jira/issues/32) - make path to cookieFile if it's not present [Mike Pountney] [[6644579](https://github.com/Netflix-Skunkworks/go-jira/commit/6644579)]
|
* Fixes [#32](https://github.com/Netflix-Skunkworks/go-jira/issues/32) - make path to cookieFile if it's not present [Mike Pountney] [[6644579](https://github.com/Netflix-Skunkworks/go-jira/commit/6644579)]
|
||||||
|
|||||||
@@ -1,29 +1,41 @@
|
|||||||
PLATFORMS= \
|
PLATFORMS= \
|
||||||
freebsd-386 \
|
|
||||||
freebsd-amd64 \
|
freebsd-amd64 \
|
||||||
freebsd-arm \
|
|
||||||
linux-386 \
|
linux-386 \
|
||||||
linux-amd64 \
|
linux-amd64 \
|
||||||
linux-arm \
|
|
||||||
openbsd-386 \
|
|
||||||
openbsd-amd64 \
|
|
||||||
windows-386 \
|
windows-386 \
|
||||||
windows-amd64 \
|
windows-amd64 \
|
||||||
darwin-386 \
|
|
||||||
darwin-amd64 \
|
darwin-amd64 \
|
||||||
$(NULL)
|
$(NULL)
|
||||||
|
|
||||||
DIST=$(shell pwd)/dist
|
# freebsd-386 \
|
||||||
|
# freebsd-arm \
|
||||||
|
# linux-arm \
|
||||||
|
# openbsd-386 \
|
||||||
|
# openbsd-amd64 \
|
||||||
|
# darwin-386
|
||||||
|
|
||||||
export GOPATH=$(shell pwd)
|
|
||||||
|
|
||||||
GOBIN ?= $(shell pwd)
|
|
||||||
NAME=jira
|
NAME=jira
|
||||||
|
|
||||||
BIN ?= $(GOBIN)/$(NAME)
|
OS=$(shell uname -s)
|
||||||
|
ifeq ($(filter CYGWIN%,$(OS)),$(OS))
|
||||||
|
export CWD=$(shell cygpath -wa .)
|
||||||
|
export SEP=\\
|
||||||
|
export CYGWIN=winsymlinks:native
|
||||||
|
BIN ?= $(GOBIN)$(SEP)$(NAME).exe
|
||||||
|
else
|
||||||
|
export CWD=$(shell pwd)
|
||||||
|
export SEP=/
|
||||||
|
BIN ?= $(GOBIN)$(SEP)$(NAME)
|
||||||
|
endif
|
||||||
|
|
||||||
CURVER ?= $(shell [ -d .git ] && git describe --abbrev=0 --tags || grep ^\#\# CHANGELOG.md | awk '{print $$2; exit}')
|
export GOPATH=$(CWD)
|
||||||
LDFLAGS:=-X jira.VERSION=$(patsubst v%,%,$(CURVER)) -w
|
|
||||||
|
DIST=$(CWD)$(SEP)dist
|
||||||
|
|
||||||
|
GOBIN ?= $(CWD)
|
||||||
|
|
||||||
|
CURVER ?= $(patsubst v%,%,$(shell [ -d .git ] && git describe --abbrev=0 --tags || grep ^\#\# CHANGELOG.md | awk '{print $$2; exit}'))
|
||||||
|
LDFLAGS:=-X jira.VERSION=$(CURVER) -w
|
||||||
|
|
||||||
# use make DEBUG=1 and you can get a debuggable golang binary
|
# use make DEBUG=1 and you can get a debuggable golang binary
|
||||||
# see https://github.com/mailgun/godebug
|
# see https://github.com/mailgun/godebug
|
||||||
@@ -34,16 +46,16 @@ else
|
|||||||
endif
|
endif
|
||||||
|
|
||||||
build: src/github.com/Netflix-Skunkworks/go-jira
|
build: src/github.com/Netflix-Skunkworks/go-jira
|
||||||
$(GOBUILD) -o $(BIN) main/main.go
|
$(GOBUILD) -o '$(BIN)' main/main.go
|
||||||
|
|
||||||
src/%:
|
src/%:
|
||||||
mkdir -p $(@D)
|
mkdir -p $(@D)
|
||||||
test -L $@ || ln -sf ../../.. $@
|
test -L $@ || ln -sf '$(GOPATH)' $@
|
||||||
go get -v $* $*/main
|
go get -v $* $*/main
|
||||||
|
|
||||||
cross-setup:
|
cross-setup:
|
||||||
for p in $(PLATFORMS); do \
|
for p in $(PLATFORMS); do \
|
||||||
echo "Building for $$p"; \
|
echo Building for $$p"; \
|
||||||
cd $(GOROOT)/src && sudo GOROOT_BOOTSTRAP=$(GOROOT) GOOS=$${p/-*/} GOARCH=$${p/*-/} bash ./make.bash --no-clean; \
|
cd $(GOROOT)/src && sudo GOROOT_BOOTSTRAP=$(GOROOT) GOOS=$${p/-*/} GOARCH=$${p/*-/} bash ./make.bash --no-clean; \
|
||||||
done
|
done
|
||||||
|
|
||||||
@@ -54,6 +66,7 @@ all:
|
|||||||
echo "Building for $$p"; \
|
echo "Building for $$p"; \
|
||||||
${MAKE} build GOOS=$${p/-*/} GOARCH=$${p/*-/} BIN=$(DIST)/$(NAME)-$$p; \
|
${MAKE} build GOOS=$${p/-*/} GOARCH=$${p/*-/} BIN=$(DIST)/$(NAME)-$$p; \
|
||||||
done
|
done
|
||||||
|
for x in $(DIST)/jira-windows-*; do mv $$x $$x.exe; done
|
||||||
|
|
||||||
fmt:
|
fmt:
|
||||||
gofmt -s -w main/*.go *.go
|
gofmt -s -w main/*.go *.go
|
||||||
@@ -65,7 +78,7 @@ NEWVER ?= $(shell echo $(CURVER) | awk -F. '{print $$1"."$$2"."$$3+1}')
|
|||||||
TODAY := $(shell date +%Y-%m-%d)
|
TODAY := $(shell date +%Y-%m-%d)
|
||||||
|
|
||||||
changes:
|
changes:
|
||||||
@git log --pretty=format:"* %s [%cn] [%h]" --no-merges ^$(CURVER) HEAD main/*.go *.go | grep -vE 'gofmt|go fmt'
|
@git log --pretty=format:"* %s [%cn] [%h]" --no-merges ^v$(CURVER) HEAD main/*.go *.go | grep -vE 'gofmt|go fmt'
|
||||||
|
|
||||||
update-changelog:
|
update-changelog:
|
||||||
@echo "# Changelog" > CHANGELOG.md.new; \
|
@echo "# Changelog" > CHANGELOG.md.new; \
|
||||||
@@ -81,7 +94,7 @@ update-changelog:
|
|||||||
git tag v$(NEWVER)
|
git tag v$(NEWVER)
|
||||||
|
|
||||||
version:
|
version:
|
||||||
@echo $(patsubst v%,%,$(CURVER))
|
@echo $(CURVER)
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
rm -rf pkg dist bin src ./$(NAME)
|
rm -rf pkg dist bin src ./$(NAME)
|
||||||
|
|||||||
@@ -69,25 +69,13 @@ You can download one of the pre-built binaries for **go-jira** [here](https://gi
|
|||||||
|
|
||||||
* **NOTE** You will need **`go-1.4.1`** minimum
|
* **NOTE** You will need **`go-1.4.1`** minimum
|
||||||
|
|
||||||
* If you do not have a **GOPATH** setup, these are simple build steps:
|
* To build the `jira` binary the current directory just run:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
git clone git@github.com:Netflix-Skunkworks/go-jira.git
|
make
|
||||||
cd go-jira
|
|
||||||
export GOPATH=$(pwd)
|
|
||||||
export GOBIN=$GOPATH/bin
|
|
||||||
export PATH=$GOBIN:$PATH
|
|
||||||
cd src/github.com/Netflix-Skunkworks/go-jira/jira
|
|
||||||
go get -v
|
|
||||||
```
|
```
|
||||||
|
* To install the binary to you ~/bin directory you can run:
|
||||||
* If you do have a **GOPATH** setup, these are the standard steps to build:
|
```bash
|
||||||
|
make install
|
||||||
```
|
|
||||||
cd $GOPATH
|
|
||||||
git clone git@github.com:Netflix-Skunkworks/go-jira.git src/github.com/Netflix-Skunkworks/go-jira
|
|
||||||
cd src/github.com/Netflix-Skunkworks/go-jira/jira
|
|
||||||
go get -v
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## Configuration
|
## Configuration
|
||||||
@@ -117,6 +105,8 @@ endpoint: https://jira.mycompany.com
|
|||||||
EOM
|
EOM
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Then use `jira login` to authenticate yourself.
|
||||||
|
|
||||||
### Dynamic Configuration
|
### Dynamic Configuration
|
||||||
|
|
||||||
If the **.jira.d/config.yml** file is executable, then **go-jira** will attempt to execute the file and use the stdout for configuration. You can use this to customize templates or other overrides depending on what type of operation you are running. For example if you would like to use the "table" template when ever you run `jira ls`, then you can create a template like this:
|
If the **.jira.d/config.yml** file is executable, then **go-jira** will attempt to execute the file and use the stdout for configuration. You can use this to customize templates or other overrides depending on what type of operation you are running. For example if you would like to use the "table" template when ever you run `jira ls`, then you can create a template like this:
|
||||||
@@ -167,6 +157,26 @@ When running a command like `jira edit` it will look through the current directo
|
|||||||
if found it will use that file as the template, otherwise it will use the default **edit** template hard-coded into **go-jira**. You can export the default
|
if found it will use that file as the template, otherwise it will use the default **edit** template hard-coded into **go-jira**. You can export the default
|
||||||
hard-coded templates with `jira export-templates` which will write them to **~/.jira.d/templates/**.
|
hard-coded templates with `jira export-templates` which will write them to **~/.jira.d/templates/**.
|
||||||
|
|
||||||
|
#### Writing/Editing Templates
|
||||||
|
|
||||||
|
First the basic templating functionality is defined by the Go language 'text/template' library. The library reference documentation can be found [here](https://golang.org/pkg/text/template/), and there is a good primer document [here](https://gohugo.io/templates/go-templates/). `go-jira` also provides a few extra helper functions to make it a bit easlier to format the data, those functions are defined [here](https://github.com/Netflix-Skunkworks/go-jira/blob/master/util.go#L133).
|
||||||
|
|
||||||
|
Knowing what data and fields are available to any given template is not obvious. The easiest approach to determine what is available is to use the `debug` template on any given operation. For eample to find out what is available to the "view" templates, you can use:
|
||||||
|
```
|
||||||
|
jira view GOJIRA-321 -t debug
|
||||||
|
```
|
||||||
|
|
||||||
|
This will print out the data in JSON format that is available to the template. You can do this for any other operation, like "list":
|
||||||
|
```
|
||||||
|
jira list -t debug
|
||||||
|
```
|
||||||
|
|
||||||
|
Figuring out what is available to input templates (like for the `create` operation) is a bit more tricky, but similar. To find the data available for a `create` template you can run:
|
||||||
|
```
|
||||||
|
jira create --dryrun -t debug --editor /bin/cat
|
||||||
|
```
|
||||||
|
This will attempt to fetch metadata for your default project (you can provide any options that you would normally specify for the `create` operation). It uses the `--dryrun` option to prevent any actual updates being sent to Jira. The `-t debug` is like before to cause the input to be serialized to JSON and printed for your inspection. Finally the `--editor /bin/cat` will cause `go-jira` to just print the template rather than open up an editor and wait for you to edit/save it.
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
```
|
```
|
||||||
@@ -205,7 +215,7 @@ General Options:
|
|||||||
-e --endpoint=URI URI to use for jira
|
-e --endpoint=URI URI to use for jira
|
||||||
-h --help Show this usage
|
-h --help Show this usage
|
||||||
-t --template=FILE Template file to use for output/editing
|
-t --template=FILE Template file to use for output/editing
|
||||||
-u --user=USER Username to use for authenticaion (default: $USER)
|
-u --user=USER Username to use for authentication (default: $USER)
|
||||||
-v --verbose Increase output logging
|
-v --verbose Increase output logging
|
||||||
|
|
||||||
Query Options:
|
Query Options:
|
||||||
|
|||||||
@@ -6,8 +6,8 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/kballard/go-shellquote"
|
"github.com/kballard/go-shellquote"
|
||||||
"github.com/op/go-logging"
|
|
||||||
"gopkg.in/coryb/yaml.v2"
|
"gopkg.in/coryb/yaml.v2"
|
||||||
|
"gopkg.in/op/go-logging.v1"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/cookiejar"
|
"net/http/cookiejar"
|
||||||
@@ -15,6 +15,7 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"path"
|
"path"
|
||||||
|
"path/filepath"
|
||||||
"runtime"
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
@@ -33,7 +34,7 @@ type Cli struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func New(opts map[string]interface{}) *Cli {
|
func New(opts map[string]interface{}) *Cli {
|
||||||
homedir := os.Getenv("HOME")
|
homedir := homedir()
|
||||||
cookieJar, _ := cookiejar.New(nil)
|
cookieJar, _ := cookiejar.New(nil)
|
||||||
endpoint, _ := opts["endpoint"].(string)
|
endpoint, _ := opts["endpoint"].(string)
|
||||||
url, _ := url.Parse(strings.TrimRight(endpoint, "/"))
|
url, _ := url.Parse(strings.TrimRight(endpoint, "/"))
|
||||||
@@ -53,7 +54,7 @@ func New(opts map[string]interface{}) *Cli {
|
|||||||
cli := &Cli{
|
cli := &Cli{
|
||||||
endpoint: url,
|
endpoint: url,
|
||||||
opts: opts,
|
opts: opts,
|
||||||
cookieFile: fmt.Sprintf("%s/.jira.d/cookies.js", homedir),
|
cookieFile: filepath.Join(homedir, ".jira.d", "cookies.js"),
|
||||||
ua: &http.Client{
|
ua: &http.Client{
|
||||||
Jar: cookieJar,
|
Jar: cookieJar,
|
||||||
Transport: transport,
|
Transport: transport,
|
||||||
@@ -100,15 +101,15 @@ func (c *Cli) loadCookies() []*http.Cookie {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("Failed to open %s: %s", c.cookieFile, err)
|
log.Errorf("Failed to open %s: %s", c.cookieFile, err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
cookies := make([]*http.Cookie, 0)
|
cookies := make([]*http.Cookie, 0)
|
||||||
err = json.Unmarshal(bytes, &cookies)
|
err = json.Unmarshal(bytes, &cookies)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("Failed to parse json from file %s: %s", c.cookieFile, err)
|
log.Errorf("Failed to parse json from file %s: %s", c.cookieFile, err)
|
||||||
}
|
}
|
||||||
log.Debug("Loading Cookies: %s", cookies)
|
log.Debugf("Loading Cookies: %s", cookies)
|
||||||
return cookies
|
return cookies
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -123,7 +124,7 @@ func (c *Cli) put(uri string, content string) (*http.Response, error) {
|
|||||||
func (c *Cli) delete(uri string) (*http.Response, error) {
|
func (c *Cli) delete(uri string) (*http.Response, error) {
|
||||||
method := "DELETE"
|
method := "DELETE"
|
||||||
req, _ := http.NewRequest(method, uri, nil)
|
req, _ := http.NewRequest(method, uri, nil)
|
||||||
log.Info("%s %s", req.Method, req.URL.String())
|
log.Infof("%s %s", req.Method, req.URL.String())
|
||||||
if resp, err := c.makeRequest(req); err != nil {
|
if resp, err := c.makeRequest(req); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
} else {
|
} else {
|
||||||
@@ -142,11 +143,11 @@ func (c *Cli) makeRequestWithContent(method string, uri string, content string)
|
|||||||
buffer := bytes.NewBufferString(content)
|
buffer := bytes.NewBufferString(content)
|
||||||
req, _ := http.NewRequest(method, uri, buffer)
|
req, _ := http.NewRequest(method, uri, buffer)
|
||||||
|
|
||||||
log.Info("%s %s", req.Method, req.URL.String())
|
log.Infof("%s %s", req.Method, req.URL.String())
|
||||||
if log.IsEnabledFor(logging.DEBUG) {
|
if log.IsEnabledFor(logging.DEBUG) {
|
||||||
logBuffer := bytes.NewBuffer(make([]byte, 0, len(content)))
|
logBuffer := bytes.NewBuffer(make([]byte, 0, len(content)))
|
||||||
req.Write(logBuffer)
|
req.Write(logBuffer)
|
||||||
log.Debug("%s", logBuffer)
|
log.Debugf("%s", logBuffer)
|
||||||
// need to recreate the buffer since the offset is now at the end
|
// need to recreate the buffer since the offset is now at the end
|
||||||
// need to be able to rewind the buffer offset, dont know how yet
|
// need to be able to rewind the buffer offset, dont know how yet
|
||||||
req, _ = http.NewRequest(method, uri, bytes.NewBufferString(content))
|
req, _ = http.NewRequest(method, uri, bytes.NewBufferString(content))
|
||||||
@@ -168,11 +169,11 @@ func (c *Cli) makeRequestWithContent(method string, uri string, content string)
|
|||||||
|
|
||||||
func (c *Cli) get(uri string) (*http.Response, error) {
|
func (c *Cli) get(uri string) (*http.Response, error) {
|
||||||
req, _ := http.NewRequest("GET", uri, nil)
|
req, _ := http.NewRequest("GET", uri, nil)
|
||||||
log.Info("%s %s", req.Method, req.URL.String())
|
log.Infof("%s %s", req.Method, req.URL.String())
|
||||||
if log.IsEnabledFor(logging.DEBUG) {
|
if log.IsEnabledFor(logging.DEBUG) {
|
||||||
logBuffer := bytes.NewBuffer(make([]byte, 0))
|
logBuffer := bytes.NewBuffer(make([]byte, 0))
|
||||||
req.Write(logBuffer)
|
req.Write(logBuffer)
|
||||||
log.Debug("%s", logBuffer)
|
log.Debugf("%s", logBuffer)
|
||||||
}
|
}
|
||||||
|
|
||||||
if resp, err := c.makeRequest(req); err != nil {
|
if resp, err := c.makeRequest(req); err != nil {
|
||||||
@@ -191,11 +192,11 @@ func (c *Cli) get(uri string) (*http.Response, error) {
|
|||||||
func (c *Cli) makeRequest(req *http.Request) (resp *http.Response, err error) {
|
func (c *Cli) makeRequest(req *http.Request) (resp *http.Response, err error) {
|
||||||
req.Header.Set("Content-Type", "application/json")
|
req.Header.Set("Content-Type", "application/json")
|
||||||
if resp, err = c.ua.Do(req); err != nil {
|
if resp, err = c.ua.Do(req); err != nil {
|
||||||
log.Error("Failed to %s %s: %s", req.Method, req.URL.String(), err)
|
log.Errorf("Failed to %s %s: %s", req.Method, req.URL.String(), err)
|
||||||
return nil, err
|
return nil, err
|
||||||
} else {
|
} else {
|
||||||
if resp.StatusCode < 200 || resp.StatusCode >= 300 && resp.StatusCode != 401 {
|
if resp.StatusCode < 200 || resp.StatusCode >= 300 && resp.StatusCode != 401 {
|
||||||
log.Error("response status: %s", resp.Status)
|
log.Errorf("response status: %s", resp.Status)
|
||||||
}
|
}
|
||||||
|
|
||||||
runtime.SetFinalizer(resp, func(r *http.Response) {
|
runtime.SetFinalizer(resp, func(r *http.Response) {
|
||||||
@@ -213,33 +214,33 @@ func (c *Cli) GetTemplate(name string) string {
|
|||||||
return c.getTemplate(name)
|
return c.getTemplate(name)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getLookedUpTemplate(name string, dflt string) string {
|
||||||
|
if file, err := FindClosestParentPath(filepath.Join(".jira.d", "templates", name)); err == nil {
|
||||||
|
return readFile(file)
|
||||||
|
}
|
||||||
|
if _, err := os.Stat(fmt.Sprintf("/etc/go-jira/templates/%s", name)); err == nil {
|
||||||
|
file := fmt.Sprintf("/etc/go-jira/templates/%s", name)
|
||||||
|
return readFile(file)
|
||||||
|
}
|
||||||
|
return dflt
|
||||||
|
}
|
||||||
|
|
||||||
func (c *Cli) getTemplate(name string) string {
|
func (c *Cli) getTemplate(name string) string {
|
||||||
if override, ok := c.opts["template"].(string); ok {
|
if override, ok := c.opts["template"].(string); ok {
|
||||||
if _, err := os.Stat(override); err == nil {
|
if _, err := os.Stat(override); err == nil {
|
||||||
return readFile(override)
|
return readFile(override)
|
||||||
} else {
|
} else {
|
||||||
if file, err := FindClosestParentPath(fmt.Sprintf(".jira.d/templates/%s", override)); err == nil {
|
if t := getLookedUpTemplate(override, all_templates[override]); t != "" {
|
||||||
return readFile(file)
|
return t
|
||||||
}
|
|
||||||
if dflt, ok := all_templates[override]; ok {
|
|
||||||
return dflt
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if file, err := FindClosestParentPath(fmt.Sprintf(".jira.d/templates/%s", name)); err != nil {
|
// create-bug etc are special, if we dont find it in the path
|
||||||
// create-bug etc are special, if we dont find it in the path
|
// then just return the create template
|
||||||
// then just return a generic create template
|
if strings.HasPrefix(name, "create-") {
|
||||||
if strings.HasPrefix(name, "create-") {
|
return getLookedUpTemplate(name, c.getTemplate("create"))
|
||||||
if file, err := FindClosestParentPath(".jira.d/templates/create"); err != nil {
|
|
||||||
return all_templates["create"]
|
|
||||||
} else {
|
|
||||||
return readFile(file)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return all_templates[name]
|
|
||||||
} else {
|
|
||||||
return readFile(file)
|
|
||||||
}
|
}
|
||||||
|
return getLookedUpTemplate(name, all_templates[name])
|
||||||
}
|
}
|
||||||
|
|
||||||
type NoChangesFound struct{}
|
type NoChangesFound struct{}
|
||||||
@@ -250,23 +251,35 @@ func (f NoChangesFound) Error() string {
|
|||||||
|
|
||||||
func (c *Cli) editTemplate(template string, tmpFilePrefix string, templateData map[string]interface{}, templateProcessor func(string) error) error {
|
func (c *Cli) editTemplate(template string, tmpFilePrefix string, templateData map[string]interface{}, templateProcessor func(string) error) error {
|
||||||
|
|
||||||
tmpdir := fmt.Sprintf("%s/.jira.d/tmp", os.Getenv("HOME"))
|
tmpdir := filepath.Join(homedir(), ".jira.d", "tmp")
|
||||||
if err := mkdir(tmpdir); err != nil {
|
if err := mkdir(tmpdir); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
fh, err := ioutil.TempFile(tmpdir, tmpFilePrefix)
|
fh, err := ioutil.TempFile(tmpdir, tmpFilePrefix)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("Failed to make temp file in %s: %s", tmpdir, err)
|
log.Errorf("Failed to make temp file in %s: %s", tmpdir, err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
defer fh.Close()
|
|
||||||
|
|
||||||
tmpFileName := fmt.Sprintf("%s.yml", fh.Name())
|
oldFileName := fh.Name()
|
||||||
if err := os.Rename(fh.Name(), tmpFileName); err != nil {
|
tmpFileName := fmt.Sprintf("%s.yml", oldFileName)
|
||||||
log.Error("Failed to rename %s to %s: %s", fh.Name(), fmt.Sprintf("%s.yml", fh.Name()), err)
|
|
||||||
|
// close tmpfile so we can rename on windows
|
||||||
|
fh.Close()
|
||||||
|
|
||||||
|
if err := os.Rename(oldFileName, tmpFileName); err != nil {
|
||||||
|
log.Errorf("Failed to rename %s to %s: %s", oldFileName, tmpFileName, err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fh, err = os.OpenFile(tmpFileName, os.O_RDWR|os.O_EXCL, 0600)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("Failed to reopen temp file file in %s: %s", tmpFileName, err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
defer fh.Close()
|
||||||
defer func() {
|
defer func() {
|
||||||
os.Remove(tmpFileName)
|
os.Remove(tmpFileName)
|
||||||
}()
|
}()
|
||||||
@@ -301,11 +314,11 @@ func (c *Cli) editTemplate(template string, tmpFilePrefix string, templateData m
|
|||||||
if editing {
|
if editing {
|
||||||
shell, _ := shellquote.Split(editor)
|
shell, _ := shellquote.Split(editor)
|
||||||
shell = append(shell, tmpFileName)
|
shell = append(shell, tmpFileName)
|
||||||
log.Debug("Running: %#v", shell)
|
log.Debugf("Running: %#v", shell)
|
||||||
cmd := exec.Command(shell[0], shell[1:]...)
|
cmd := exec.Command(shell[0], shell[1:]...)
|
||||||
cmd.Stdout, cmd.Stderr, cmd.Stdin = os.Stdout, os.Stderr, os.Stdin
|
cmd.Stdout, cmd.Stderr, cmd.Stdin = os.Stdout, os.Stderr, os.Stdin
|
||||||
if err := cmd.Run(); err != nil {
|
if err := cmd.Run(); err != nil {
|
||||||
log.Error("Failed to edit template with %s: %s", editor, err)
|
log.Errorf("Failed to edit template with %s: %s", editor, err)
|
||||||
if promptYN("edit again?", true) {
|
if promptYN("edit again?", true) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -321,14 +334,14 @@ func (c *Cli) editTemplate(template string, tmpFilePrefix string, templateData m
|
|||||||
|
|
||||||
edited := make(map[string]interface{})
|
edited := make(map[string]interface{})
|
||||||
if fh, err := ioutil.ReadFile(tmpFileName); err != nil {
|
if fh, err := ioutil.ReadFile(tmpFileName); err != nil {
|
||||||
log.Error("Failed to read tmpfile %s: %s", tmpFileName, err)
|
log.Errorf("Failed to read tmpfile %s: %s", tmpFileName, err)
|
||||||
if editing && promptYN("edit again?", true) {
|
if editing && promptYN("edit again?", true) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
return err
|
return err
|
||||||
} else {
|
} else {
|
||||||
if err := yaml.Unmarshal(fh, &edited); err != nil {
|
if err := yaml.Unmarshal(fh, &edited); err != nil {
|
||||||
log.Error("Failed to parse YAML: %s", err)
|
log.Errorf("Failed to parse YAML: %s", err)
|
||||||
if editing && promptYN("edit again?", true) {
|
if editing && promptYN("edit again?", true) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -346,7 +359,7 @@ func (c *Cli) editTemplate(template string, tmpFilePrefix string, templateData m
|
|||||||
// you can add the "abort: true" flag to the document
|
// you can add the "abort: true" flag to the document
|
||||||
// and we will abort now
|
// and we will abort now
|
||||||
if val, ok := edited["abort"].(bool); ok && val {
|
if val, ok := edited["abort"].(bool); ok && val {
|
||||||
log.Info("abort flag found in template, quiting")
|
log.Infof("abort flag found in template, quiting")
|
||||||
return fmt.Errorf("abort flag found in template, quiting")
|
return fmt.Errorf("abort flag found in template, quiting")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -356,7 +369,7 @@ func (c *Cli) editTemplate(template string, tmpFilePrefix string, templateData m
|
|||||||
for k := range f {
|
for k := range f {
|
||||||
if _, ok := mf.(map[string]interface{})[k]; !ok {
|
if _, ok := mf.(map[string]interface{})[k]; !ok {
|
||||||
err := fmt.Errorf("Field %s is not editable", k)
|
err := fmt.Errorf("Field %s is not editable", k)
|
||||||
log.Error("%s", err)
|
log.Errorf("%s", err)
|
||||||
if editing && promptYN("edit again?", true) {
|
if editing && promptYN("edit again?", true) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -372,7 +385,7 @@ func (c *Cli) editTemplate(template string, tmpFilePrefix string, templateData m
|
|||||||
}
|
}
|
||||||
|
|
||||||
if err := templateProcessor(json); err != nil {
|
if err := templateProcessor(json); err != nil {
|
||||||
log.Error("%s", err)
|
log.Errorf("%s", err)
|
||||||
if editing && promptYN("edit again?", true) {
|
if editing && promptYN("edit again?", true) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -402,6 +415,10 @@ func (c *Cli) SaveData(data interface{}) error {
|
|||||||
|
|
||||||
func (c *Cli) ViewIssue(issue string) (interface{}, error) {
|
func (c *Cli) ViewIssue(issue string) (interface{}, error) {
|
||||||
uri := fmt.Sprintf("%s/rest/api/2/issue/%s", c.endpoint, issue)
|
uri := fmt.Sprintf("%s/rest/api/2/issue/%s", c.endpoint, issue)
|
||||||
|
if x := c.expansions(); len(x) > 0 {
|
||||||
|
uri = fmt.Sprintf("%s?expand=%s", uri, strings.Join(x, ","))
|
||||||
|
}
|
||||||
|
|
||||||
data, err := responseToJson(c.get(uri))
|
data, err := responseToJson(c.get(uri))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -418,7 +435,7 @@ func (c *Cli) FindIssues() (interface{}, error) {
|
|||||||
qbuff := bytes.NewBufferString("resolution = unresolved")
|
qbuff := bytes.NewBufferString("resolution = unresolved")
|
||||||
if project, ok := c.opts["project"]; !ok {
|
if project, ok := c.opts["project"]; !ok {
|
||||||
err := fmt.Errorf("Missing required arguments, either 'query' or 'project' are required")
|
err := fmt.Errorf("Missing required arguments, either 'query' or 'project' are required")
|
||||||
log.Error("%s", err)
|
log.Errorf("%s", err)
|
||||||
return nil, err
|
return nil, err
|
||||||
} else {
|
} else {
|
||||||
qbuff.WriteString(fmt.Sprintf(" AND project = '%s'", project))
|
qbuff.WriteString(fmt.Sprintf(" AND project = '%s'", project))
|
||||||
@@ -463,6 +480,7 @@ func (c *Cli) FindIssues() (interface{}, error) {
|
|||||||
"startAt": "0",
|
"startAt": "0",
|
||||||
"maxResults": c.opts["max_results"],
|
"maxResults": c.opts["max_results"],
|
||||||
"fields": fields,
|
"fields": fields,
|
||||||
|
"expand": c.expansions(),
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -499,3 +517,12 @@ func (c *Cli) getOptBool(optName string, dflt bool) bool {
|
|||||||
return dflt
|
return dflt
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// expansions returns a comma-separated list of values for field expansion
|
||||||
|
func (c *Cli) expansions() []string {
|
||||||
|
expansions := make([]string, 0)
|
||||||
|
if x, ok := c.opts["expand"].(string); ok {
|
||||||
|
expansions = strings.Split(x, ",")
|
||||||
|
}
|
||||||
|
return expansions
|
||||||
|
}
|
||||||
|
|||||||
+76
-75
@@ -7,6 +7,7 @@ import (
|
|||||||
"golang.org/x/crypto/ssh/terminal"
|
"golang.org/x/crypto/ssh/terminal"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httputil"
|
"net/http/httputil"
|
||||||
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
// "github.com/kr/pretty"
|
// "github.com/kr/pretty"
|
||||||
@@ -23,22 +24,22 @@ func (c *Cli) CmdLogin() error {
|
|||||||
passwd := string(pwbytes)
|
passwd := string(pwbytes)
|
||||||
|
|
||||||
req.SetBasicAuth(user, passwd)
|
req.SetBasicAuth(user, passwd)
|
||||||
log.Info("%s %s", req.Method, req.URL.String())
|
log.Infof("%s %s", req.Method, req.URL.String())
|
||||||
if resp, err := c.makeRequest(req); err != nil {
|
if resp, err := c.makeRequest(req); err != nil {
|
||||||
return err
|
return err
|
||||||
} else {
|
} else {
|
||||||
out, _ := httputil.DumpResponse(resp, true)
|
out, _ := httputil.DumpResponse(resp, true)
|
||||||
log.Debug("%s", out)
|
log.Debugf("%s", out)
|
||||||
if resp.StatusCode == 403 {
|
if resp.StatusCode == 403 {
|
||||||
// probably got this, need to redirect the user to login manually
|
// probably got this, need to redirect the user to login manually
|
||||||
// X-Authentication-Denied-Reason: CAPTCHA_CHALLENGE; login-url=https://jira/login.jsp
|
// X-Authentication-Denied-Reason: CAPTCHA_CHALLENGE; login-url=https://jira/login.jsp
|
||||||
if reason := resp.Header.Get("X-Authentication-Denied-Reason"); reason != "" {
|
if reason := resp.Header.Get("X-Authentication-Denied-Reason"); reason != "" {
|
||||||
err := fmt.Errorf("Authenticaion Failed: %s", reason)
|
err := fmt.Errorf("Authenticaion Failed: %s", reason)
|
||||||
log.Error("%s", err)
|
log.Errorf("%s", err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
err := fmt.Errorf("Authentication Failed: Unknown Reason")
|
err := fmt.Errorf("Authentication Failed: Unknown Reason")
|
||||||
log.Error("%s", err)
|
log.Errorf("%s", err)
|
||||||
return err
|
return err
|
||||||
|
|
||||||
} else if resp.StatusCode == 200 {
|
} else if resp.StatusCode == 200 {
|
||||||
@@ -59,7 +60,7 @@ func (c *Cli) CmdLogin() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *Cli) CmdFields() error {
|
func (c *Cli) CmdFields() error {
|
||||||
log.Debug("fields called")
|
log.Debugf("fields called")
|
||||||
uri := fmt.Sprintf("%s/rest/api/2/field", c.endpoint)
|
uri := fmt.Sprintf("%s/rest/api/2/field", c.endpoint)
|
||||||
data, err := responseToJson(c.get(uri))
|
data, err := responseToJson(c.get(uri))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -70,7 +71,7 @@ func (c *Cli) CmdFields() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *Cli) CmdList() error {
|
func (c *Cli) CmdList() error {
|
||||||
log.Debug("list called")
|
log.Debugf("list called")
|
||||||
if data, err := c.FindIssues(); err != nil {
|
if data, err := c.FindIssues(); err != nil {
|
||||||
return err
|
return err
|
||||||
} else {
|
} else {
|
||||||
@@ -79,7 +80,7 @@ func (c *Cli) CmdList() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *Cli) CmdView(issue string) error {
|
func (c *Cli) CmdView(issue string) error {
|
||||||
log.Debug("view called")
|
log.Debugf("view called")
|
||||||
c.Browse(issue)
|
c.Browse(issue)
|
||||||
data, err := c.ViewIssue(issue)
|
data, err := c.ViewIssue(issue)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -89,7 +90,7 @@ func (c *Cli) CmdView(issue string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *Cli) CmdEdit(issue string) error {
|
func (c *Cli) CmdEdit(issue string) error {
|
||||||
log.Debug("edit called")
|
log.Debugf("edit called")
|
||||||
|
|
||||||
uri := fmt.Sprintf("%s/rest/api/2/issue/%s/editmeta", c.endpoint, issue)
|
uri := fmt.Sprintf("%s/rest/api/2/issue/%s/editmeta", c.endpoint, issue)
|
||||||
editmeta, err := responseToJson(c.get(uri))
|
editmeta, err := responseToJson(c.get(uri))
|
||||||
@@ -114,8 +115,8 @@ func (c *Cli) CmdEdit(issue string) error {
|
|||||||
issueData,
|
issueData,
|
||||||
func(json string) error {
|
func(json string) error {
|
||||||
if c.getOptBool("dryrun", false) {
|
if c.getOptBool("dryrun", false) {
|
||||||
log.Debug("PUT: %s", json)
|
log.Debugf("PUT: %s", json)
|
||||||
log.Debug("Dryrun mode, skipping PUT")
|
log.Debugf("Dryrun mode, skipping PUT")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
resp, err := c.put(uri, json)
|
resp, err := c.put(uri, json)
|
||||||
@@ -133,7 +134,7 @@ func (c *Cli) CmdEdit(issue string) error {
|
|||||||
logBuffer := bytes.NewBuffer(make([]byte, 0))
|
logBuffer := bytes.NewBuffer(make([]byte, 0))
|
||||||
resp.Write(logBuffer)
|
resp.Write(logBuffer)
|
||||||
err := fmt.Errorf("Unexpected Response From PUT")
|
err := fmt.Errorf("Unexpected Response From PUT")
|
||||||
log.Error("%s:\n%s", err, logBuffer)
|
log.Errorf("%s:\n%s", err, logBuffer)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -141,7 +142,7 @@ func (c *Cli) CmdEdit(issue string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *Cli) CmdEditMeta(issue string) error {
|
func (c *Cli) CmdEditMeta(issue string) error {
|
||||||
log.Debug("editMeta called")
|
log.Debugf("editMeta called")
|
||||||
c.Browse(issue)
|
c.Browse(issue)
|
||||||
uri := fmt.Sprintf("%s/rest/api/2/issue/%s/editmeta", c.endpoint, issue)
|
uri := fmt.Sprintf("%s/rest/api/2/issue/%s/editmeta", c.endpoint, issue)
|
||||||
data, err := responseToJson(c.get(uri))
|
data, err := responseToJson(c.get(uri))
|
||||||
@@ -153,7 +154,7 @@ func (c *Cli) CmdEditMeta(issue string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *Cli) CmdTransitionMeta(issue string) error {
|
func (c *Cli) CmdTransitionMeta(issue string) error {
|
||||||
log.Debug("tranisionMeta called")
|
log.Debugf("tranisionMeta called")
|
||||||
c.Browse(issue)
|
c.Browse(issue)
|
||||||
uri := fmt.Sprintf("%s/rest/api/2/issue/%s/transitions?expand=transitions.fields", c.endpoint, issue)
|
uri := fmt.Sprintf("%s/rest/api/2/issue/%s/transitions?expand=transitions.fields", c.endpoint, issue)
|
||||||
data, err := responseToJson(c.get(uri))
|
data, err := responseToJson(c.get(uri))
|
||||||
@@ -166,7 +167,7 @@ func (c *Cli) CmdTransitionMeta(issue string) error {
|
|||||||
|
|
||||||
func (c *Cli) CmdIssueTypes() error {
|
func (c *Cli) CmdIssueTypes() error {
|
||||||
project := c.opts["project"].(string)
|
project := c.opts["project"].(string)
|
||||||
log.Debug("issueTypes called")
|
log.Debugf("issueTypes called")
|
||||||
uri := fmt.Sprintf("%s/rest/api/2/issue/createmeta?projectKeys=%s", c.endpoint, project)
|
uri := fmt.Sprintf("%s/rest/api/2/issue/createmeta?projectKeys=%s", c.endpoint, project)
|
||||||
data, err := responseToJson(c.get(uri))
|
data, err := responseToJson(c.get(uri))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -180,8 +181,8 @@ func (c *Cli) CmdCreateMeta() error {
|
|||||||
project := c.opts["project"].(string)
|
project := c.opts["project"].(string)
|
||||||
issuetype := c.getOptString("issuetype", "Bug")
|
issuetype := c.getOptString("issuetype", "Bug")
|
||||||
|
|
||||||
log.Debug("createMeta called")
|
log.Debugf("createMeta called")
|
||||||
uri := fmt.Sprintf("%s/rest/api/2/issue/createmeta?projectKeys=%s&issuetypeNames=%s&expand=projects.issuetypes.fields", c.endpoint, project, issuetype)
|
uri := fmt.Sprintf("%s/rest/api/2/issue/createmeta?projectKeys=%s&issuetypeNames=%s&expand=projects.issuetypes.fields", c.endpoint, project, url.QueryEscape(issuetype))
|
||||||
data, err := responseToJson(c.get(uri))
|
data, err := responseToJson(c.get(uri))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -190,7 +191,7 @@ func (c *Cli) CmdCreateMeta() error {
|
|||||||
if val, ok := data.(map[string]interface{})["projects"]; ok {
|
if val, ok := data.(map[string]interface{})["projects"]; ok {
|
||||||
if len(val.([]interface{})) == 0 {
|
if len(val.([]interface{})) == 0 {
|
||||||
err = fmt.Errorf("Project '%s' or issuetype '%s' unknown. Unable to createmeta.", project, issuetype)
|
err = fmt.Errorf("Project '%s' or issuetype '%s' unknown. Unable to createmeta.", project, issuetype)
|
||||||
log.Error("%s", err)
|
log.Errorf("%s", err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if val, ok = val.([]interface{})[0].(map[string]interface{})["issuetypes"]; ok {
|
if val, ok = val.([]interface{})[0].(map[string]interface{})["issuetypes"]; ok {
|
||||||
@@ -202,7 +203,7 @@ func (c *Cli) CmdCreateMeta() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *Cli) CmdComponents(project string) error {
|
func (c *Cli) CmdComponents(project string) error {
|
||||||
log.Debug("Components called")
|
log.Debugf("Components called")
|
||||||
uri := fmt.Sprintf("%s/rest/api/2/project/%s/components", c.endpoint, project)
|
uri := fmt.Sprintf("%s/rest/api/2/project/%s/components", c.endpoint, project)
|
||||||
data, err := responseToJson(c.get(uri))
|
data, err := responseToJson(c.get(uri))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -212,7 +213,7 @@ func (c *Cli) CmdComponents(project string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *Cli) CmdTransitions(issue string) error {
|
func (c *Cli) CmdTransitions(issue string) error {
|
||||||
log.Debug("Transitions called")
|
log.Debugf("Transitions called")
|
||||||
c.Browse(issue)
|
c.Browse(issue)
|
||||||
uri := fmt.Sprintf("%s/rest/api/2/issue/%s/transitions", c.endpoint, issue)
|
uri := fmt.Sprintf("%s/rest/api/2/issue/%s/transitions", c.endpoint, issue)
|
||||||
data, err := responseToJson(c.get(uri))
|
data, err := responseToJson(c.get(uri))
|
||||||
@@ -225,9 +226,9 @@ func (c *Cli) CmdTransitions(issue string) error {
|
|||||||
func (c *Cli) CmdCreate() error {
|
func (c *Cli) CmdCreate() error {
|
||||||
project := c.opts["project"].(string)
|
project := c.opts["project"].(string)
|
||||||
issuetype := c.getOptString("issuetype", "Bug")
|
issuetype := c.getOptString("issuetype", "Bug")
|
||||||
log.Debug("create called")
|
log.Debugf("create called")
|
||||||
|
|
||||||
uri := fmt.Sprintf("%s/rest/api/2/issue/createmeta?projectKeys=%s&issuetypeNames=%s&expand=projects.issuetypes.fields", c.endpoint, project, issuetype)
|
uri := fmt.Sprintf("%s/rest/api/2/issue/createmeta?projectKeys=%s&issuetypeNames=%s&expand=projects.issuetypes.fields", c.endpoint, project, url.QueryEscape(issuetype))
|
||||||
data, err := responseToJson(c.get(uri))
|
data, err := responseToJson(c.get(uri))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -240,13 +241,13 @@ func (c *Cli) CmdCreate() error {
|
|||||||
if val, ok := data.(map[string]interface{})["projects"]; ok {
|
if val, ok := data.(map[string]interface{})["projects"]; ok {
|
||||||
if len(val.([]interface{})) == 0 {
|
if len(val.([]interface{})) == 0 {
|
||||||
err = fmt.Errorf("Project '%s' or issuetype '%s' unknown. Unable to create issue.", project, issuetype)
|
err = fmt.Errorf("Project '%s' or issuetype '%s' unknown. Unable to create issue.", project, issuetype)
|
||||||
log.Error("%s", err)
|
log.Errorf("%s", err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if val, ok = val.([]interface{})[0].(map[string]interface{})["issuetypes"]; ok {
|
if val, ok = val.([]interface{})[0].(map[string]interface{})["issuetypes"]; ok {
|
||||||
if len(val.([]interface{})) == 0 {
|
if len(val.([]interface{})) == 0 {
|
||||||
err = fmt.Errorf("Project '%s' does not support issuetype '%s'. Unable to create issue.", project, issuetype)
|
err = fmt.Errorf("Project '%s' does not support issuetype '%s'. Unable to create issue.", project, issuetype)
|
||||||
log.Error("%s", err)
|
log.Errorf("%s", err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
issueData["meta"] = val.([]interface{})[0]
|
issueData["meta"] = val.([]interface{})[0]
|
||||||
@@ -259,11 +260,11 @@ func (c *Cli) CmdCreate() error {
|
|||||||
fmt.Sprintf("create-%s-", sanitizedType),
|
fmt.Sprintf("create-%s-", sanitizedType),
|
||||||
issueData,
|
issueData,
|
||||||
func(json string) error {
|
func(json string) error {
|
||||||
log.Debug("JSON: %s", json)
|
log.Debugf("JSON: %s", json)
|
||||||
uri := fmt.Sprintf("%s/rest/api/2/issue", c.endpoint)
|
uri := fmt.Sprintf("%s/rest/api/2/issue", c.endpoint)
|
||||||
if c.getOptBool("dryrun", false) {
|
if c.getOptBool("dryrun", false) {
|
||||||
log.Debug("POST: %s", json)
|
log.Debugf("POST: %s", json)
|
||||||
log.Debug("Dryrun mode, skipping POST")
|
log.Debugf("Dryrun mode, skipping POST")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
resp, err := c.post(uri, json)
|
resp, err := c.post(uri, json)
|
||||||
@@ -292,7 +293,7 @@ func (c *Cli) CmdCreate() error {
|
|||||||
logBuffer := bytes.NewBuffer(make([]byte, 0))
|
logBuffer := bytes.NewBuffer(make([]byte, 0))
|
||||||
resp.Write(logBuffer)
|
resp.Write(logBuffer)
|
||||||
err := fmt.Errorf("Unexpected Response From POST")
|
err := fmt.Errorf("Unexpected Response From POST")
|
||||||
log.Error("%s:\n%s", err, logBuffer)
|
log.Errorf("%s:\n%s", err, logBuffer)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -301,7 +302,7 @@ func (c *Cli) CmdCreate() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *Cli) CmdIssueLinkTypes() error {
|
func (c *Cli) CmdIssueLinkTypes() error {
|
||||||
log.Debug("Transitions called")
|
log.Debugf("Transitions called")
|
||||||
uri := fmt.Sprintf("%s/rest/api/2/issueLinkType", c.endpoint)
|
uri := fmt.Sprintf("%s/rest/api/2/issueLinkType", c.endpoint)
|
||||||
data, err := responseToJson(c.get(uri))
|
data, err := responseToJson(c.get(uri))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -311,7 +312,7 @@ func (c *Cli) CmdIssueLinkTypes() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *Cli) CmdBlocks(blocker string, issue string) error {
|
func (c *Cli) CmdBlocks(blocker string, issue string) error {
|
||||||
log.Debug("blocks called")
|
log.Debugf("blocks called")
|
||||||
|
|
||||||
json, err := jsonEncode(map[string]interface{}{
|
json, err := jsonEncode(map[string]interface{}{
|
||||||
"type": map[string]string{
|
"type": map[string]string{
|
||||||
@@ -330,8 +331,8 @@ func (c *Cli) CmdBlocks(blocker string, issue string) error {
|
|||||||
|
|
||||||
uri := fmt.Sprintf("%s/rest/api/2/issueLink", c.endpoint)
|
uri := fmt.Sprintf("%s/rest/api/2/issueLink", c.endpoint)
|
||||||
if c.getOptBool("dryrun", false) {
|
if c.getOptBool("dryrun", false) {
|
||||||
log.Debug("POST: %s", json)
|
log.Debugf("POST: %s", json)
|
||||||
log.Debug("Dryrun mode, skipping POST")
|
log.Debugf("Dryrun mode, skipping POST")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
resp, err := c.post(uri, json)
|
resp, err := c.post(uri, json)
|
||||||
@@ -347,14 +348,14 @@ func (c *Cli) CmdBlocks(blocker string, issue string) error {
|
|||||||
logBuffer := bytes.NewBuffer(make([]byte, 0))
|
logBuffer := bytes.NewBuffer(make([]byte, 0))
|
||||||
resp.Write(logBuffer)
|
resp.Write(logBuffer)
|
||||||
err := fmt.Errorf("Unexpected Response From POST")
|
err := fmt.Errorf("Unexpected Response From POST")
|
||||||
log.Error("%s:\n%s", err, logBuffer)
|
log.Errorf("%s:\n%s", err, logBuffer)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Cli) CmdDups(duplicate string, issue string) error {
|
func (c *Cli) CmdDups(duplicate string, issue string) error {
|
||||||
log.Debug("dups called")
|
log.Debugf("dups called")
|
||||||
|
|
||||||
json, err := jsonEncode(map[string]interface{}{
|
json, err := jsonEncode(map[string]interface{}{
|
||||||
"type": map[string]string{
|
"type": map[string]string{
|
||||||
@@ -373,8 +374,8 @@ func (c *Cli) CmdDups(duplicate string, issue string) error {
|
|||||||
|
|
||||||
uri := fmt.Sprintf("%s/rest/api/2/issueLink", c.endpoint)
|
uri := fmt.Sprintf("%s/rest/api/2/issueLink", c.endpoint)
|
||||||
if c.getOptBool("dryrun", false) {
|
if c.getOptBool("dryrun", false) {
|
||||||
log.Debug("POST: %s", json)
|
log.Debugf("POST: %s", json)
|
||||||
log.Debug("Dryrun mode, skipping POST")
|
log.Debugf("Dryrun mode, skipping POST")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
resp, err := c.post(uri, json)
|
resp, err := c.post(uri, json)
|
||||||
@@ -390,14 +391,14 @@ func (c *Cli) CmdDups(duplicate string, issue string) error {
|
|||||||
logBuffer := bytes.NewBuffer(make([]byte, 0))
|
logBuffer := bytes.NewBuffer(make([]byte, 0))
|
||||||
resp.Write(logBuffer)
|
resp.Write(logBuffer)
|
||||||
err := fmt.Errorf("Unexpected Response From POST")
|
err := fmt.Errorf("Unexpected Response From POST")
|
||||||
log.Error("%s:\n%s", err, logBuffer)
|
log.Errorf("%s:\n%s", err, logBuffer)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Cli) CmdWatch(issue string, watcher string, remove bool) error {
|
func (c *Cli) CmdWatch(issue string, watcher string, remove bool) error {
|
||||||
log.Debug("watch called: watcher: %q, remove: %n", watcher, remove)
|
log.Debugf("watch called: watcher: %q, remove: %n", watcher, remove)
|
||||||
|
|
||||||
var uri string
|
var uri string
|
||||||
json, err := jsonEncode(watcher)
|
json, err := jsonEncode(watcher)
|
||||||
@@ -407,11 +408,11 @@ func (c *Cli) CmdWatch(issue string, watcher string, remove bool) error {
|
|||||||
|
|
||||||
if c.getOptBool("dryrun", false) {
|
if c.getOptBool("dryrun", false) {
|
||||||
if !remove {
|
if !remove {
|
||||||
log.Debug("POST: %s", json)
|
log.Debugf("POST: %s", json)
|
||||||
log.Debug("Dryrun mode, skipping POST")
|
log.Debugf("Dryrun mode, skipping POST")
|
||||||
} else {
|
} else {
|
||||||
log.Debug("DELETE: %s", watcher)
|
log.Debugf("DELETE: %s", watcher)
|
||||||
log.Debug("Dryrun mode, skipping POST")
|
log.Debugf("Dryrun mode, skipping POST")
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -440,23 +441,23 @@ func (c *Cli) CmdWatch(issue string, watcher string, remove bool) error {
|
|||||||
} else {
|
} else {
|
||||||
err = fmt.Errorf("Unexpected Response From DELETE")
|
err = fmt.Errorf("Unexpected Response From DELETE")
|
||||||
}
|
}
|
||||||
log.Error("%s:\n%s", err, logBuffer)
|
log.Errorf("%s:\n%s", err, logBuffer)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Cli) CmdVote(issue string, up bool) error {
|
func (c *Cli) CmdVote(issue string, up bool) error {
|
||||||
log.Debug("vote called, with up: %n", up)
|
log.Debugf("vote called, with up: %n", up)
|
||||||
|
|
||||||
uri := fmt.Sprintf("%s/rest/api/2/issue/%s/votes", c.endpoint, issue)
|
uri := fmt.Sprintf("%s/rest/api/2/issue/%s/votes", c.endpoint, issue)
|
||||||
if c.getOptBool("dryrun", false) {
|
if c.getOptBool("dryrun", false) {
|
||||||
if up {
|
if up {
|
||||||
log.Debug("POST: %s", "")
|
log.Debugf("POST: %s", "")
|
||||||
log.Debug("Dryrun mode, skipping POST")
|
log.Debugf("Dryrun mode, skipping POST")
|
||||||
} else {
|
} else {
|
||||||
log.Debug("DELETE: %s", "")
|
log.Debugf("DELETE: %s", "")
|
||||||
log.Debug("Dryrun mode, skipping DELETE")
|
log.Debugf("Dryrun mode, skipping DELETE")
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -483,14 +484,14 @@ func (c *Cli) CmdVote(issue string, up bool) error {
|
|||||||
} else {
|
} else {
|
||||||
err = fmt.Errorf("Unexpected Response From DELETE")
|
err = fmt.Errorf("Unexpected Response From DELETE")
|
||||||
}
|
}
|
||||||
log.Error("%s:\n%s", err, logBuffer)
|
log.Errorf("%s:\n%s", err, logBuffer)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Cli) CmdTransition(issue string, trans string) error {
|
func (c *Cli) CmdTransition(issue string, trans string) error {
|
||||||
log.Debug("transition called")
|
log.Debugf("transition called")
|
||||||
uri := fmt.Sprintf("%s/rest/api/2/issue/%s/transitions?expand=transitions.fields", c.endpoint, issue)
|
uri := fmt.Sprintf("%s/rest/api/2/issue/%s/transitions?expand=transitions.fields", c.endpoint, issue)
|
||||||
data, err := responseToJson(c.get(uri))
|
data, err := responseToJson(c.get(uri))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -513,17 +514,17 @@ func (c *Cli) CmdTransition(issue string, trans string) error {
|
|||||||
}
|
}
|
||||||
if transId == "" {
|
if transId == "" {
|
||||||
err := fmt.Errorf("Invalid Transition '%s', Available: %s", trans, strings.Join(found, ", "))
|
err := fmt.Errorf("Invalid Transition '%s', Available: %s", trans, strings.Join(found, ", "))
|
||||||
log.Error("%s", err)
|
log.Errorf("%s", err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
handlePost := func(json string) error {
|
handlePost := func(json string) error {
|
||||||
log.Debug("POST: %s", json)
|
log.Debugf("POST: %s", json)
|
||||||
// os.Exit(0)
|
// os.Exit(0)
|
||||||
uri = fmt.Sprintf("%s/rest/api/2/issue/%s/transitions", c.endpoint, issue)
|
uri = fmt.Sprintf("%s/rest/api/2/issue/%s/transitions", c.endpoint, issue)
|
||||||
if c.getOptBool("dryrun", false) {
|
if c.getOptBool("dryrun", false) {
|
||||||
log.Debug("POST: %s", json)
|
log.Debugf("POST: %s", json)
|
||||||
log.Debug("Dryrun mode, skipping POST")
|
log.Debugf("Dryrun mode, skipping POST")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
resp, err := c.post(uri, json)
|
resp, err := c.post(uri, json)
|
||||||
@@ -539,7 +540,7 @@ func (c *Cli) CmdTransition(issue string, trans string) error {
|
|||||||
logBuffer := bytes.NewBuffer(make([]byte, 0))
|
logBuffer := bytes.NewBuffer(make([]byte, 0))
|
||||||
resp.Write(logBuffer)
|
resp.Write(logBuffer)
|
||||||
err := fmt.Errorf("Unexpected Response From POST")
|
err := fmt.Errorf("Unexpected Response From POST")
|
||||||
log.Error("%s:\n%s", err, logBuffer)
|
log.Errorf("%s:\n%s", err, logBuffer)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
@@ -568,14 +569,14 @@ func (c *Cli) CmdTransition(issue string, trans string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *Cli) CmdComment(issue string) error {
|
func (c *Cli) CmdComment(issue string) error {
|
||||||
log.Debug("comment called")
|
log.Debugf("comment called")
|
||||||
|
|
||||||
handlePost := func(json string) error {
|
handlePost := func(json string) error {
|
||||||
log.Debug("JSON: %s", json)
|
log.Debugf("JSON: %s", json)
|
||||||
uri := fmt.Sprintf("%s/rest/api/2/issue/%s/comment", c.endpoint, issue)
|
uri := fmt.Sprintf("%s/rest/api/2/issue/%s/comment", c.endpoint, issue)
|
||||||
if c.getOptBool("dryrun", false) {
|
if c.getOptBool("dryrun", false) {
|
||||||
log.Debug("POST: %s", json)
|
log.Debugf("POST: %s", json)
|
||||||
log.Debug("Dryrun mode, skipping POST")
|
log.Debugf("Dryrun mode, skipping POST")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
resp, err := c.post(uri, json)
|
resp, err := c.post(uri, json)
|
||||||
@@ -593,7 +594,7 @@ func (c *Cli) CmdComment(issue string) error {
|
|||||||
logBuffer := bytes.NewBuffer(make([]byte, 0))
|
logBuffer := bytes.NewBuffer(make([]byte, 0))
|
||||||
resp.Write(logBuffer)
|
resp.Write(logBuffer)
|
||||||
err := fmt.Errorf("Unexpected Response From POST")
|
err := fmt.Errorf("Unexpected Response From POST")
|
||||||
log.Error("%s:\n%s", err, logBuffer)
|
log.Errorf("%s:\n%s", err, logBuffer)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -618,7 +619,7 @@ func (c *Cli) CmdComment(issue string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *Cli) CmdComponent(action string, project string, name string, desc string, lead string) error {
|
func (c *Cli) CmdComponent(action string, project string, name string, desc string, lead string) error {
|
||||||
log.Debug("component called")
|
log.Debugf("component called")
|
||||||
|
|
||||||
switch action {
|
switch action {
|
||||||
case "add":
|
case "add":
|
||||||
@@ -638,8 +639,8 @@ func (c *Cli) CmdComponent(action string, project string, name string, desc stri
|
|||||||
|
|
||||||
uri := fmt.Sprintf("%s/rest/api/2/component", c.endpoint)
|
uri := fmt.Sprintf("%s/rest/api/2/component", c.endpoint)
|
||||||
if c.getOptBool("dryrun", false) {
|
if c.getOptBool("dryrun", false) {
|
||||||
log.Debug("POST: %s", json)
|
log.Debugf("POST: %s", json)
|
||||||
log.Debug("Dryrun mode, skipping POST")
|
log.Debugf("Dryrun mode, skipping POST")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
resp, err := c.post(uri, json)
|
resp, err := c.post(uri, json)
|
||||||
@@ -654,25 +655,25 @@ func (c *Cli) CmdComponent(action string, project string, name string, desc stri
|
|||||||
logBuffer := bytes.NewBuffer(make([]byte, 0))
|
logBuffer := bytes.NewBuffer(make([]byte, 0))
|
||||||
resp.Write(logBuffer)
|
resp.Write(logBuffer)
|
||||||
err := fmt.Errorf("Unexpected Response From POST")
|
err := fmt.Errorf("Unexpected Response From POST")
|
||||||
log.Error("%s:\n%s", err, logBuffer)
|
log.Errorf("%s:\n%s", err, logBuffer)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Cli) CmdLabels(action string, issue string, labels []string) error {
|
func (c *Cli) CmdLabels(action string, issue string, labels []string) error {
|
||||||
log.Debug("label called")
|
log.Debugf("label called")
|
||||||
|
|
||||||
if action != "add" && action != "remove" && action != "set" {
|
if action != "add" && action != "remove" && action != "set" {
|
||||||
return fmt.Errorf("action must be 'add', 'set' or 'remove': %q is invalid", action)
|
return fmt.Errorf("action must be 'add', 'set' or 'remove': %q is invalid", action)
|
||||||
}
|
}
|
||||||
|
|
||||||
handlePut := func(json string) error {
|
handlePut := func(json string) error {
|
||||||
log.Debug("JSON: %s", json)
|
log.Debugf("JSON: %s", json)
|
||||||
uri := fmt.Sprintf("%s/rest/api/2/issue/%s", c.endpoint, issue)
|
uri := fmt.Sprintf("%s/rest/api/2/issue/%s", c.endpoint, issue)
|
||||||
if c.getOptBool("dryrun", false) {
|
if c.getOptBool("dryrun", false) {
|
||||||
log.Debug("PUT: %s", json)
|
log.Debugf("PUT: %s", json)
|
||||||
log.Debug("Dryrun mode, skipping POST")
|
log.Debugf("Dryrun mode, skipping POST")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
resp, err := c.put(uri, json)
|
resp, err := c.put(uri, json)
|
||||||
@@ -690,7 +691,7 @@ func (c *Cli) CmdLabels(action string, issue string, labels []string) error {
|
|||||||
logBuffer := bytes.NewBuffer(make([]byte, 0))
|
logBuffer := bytes.NewBuffer(make([]byte, 0))
|
||||||
resp.Write(logBuffer)
|
resp.Write(logBuffer)
|
||||||
err := fmt.Errorf("Unexpected Response From PUT")
|
err := fmt.Errorf("Unexpected Response From PUT")
|
||||||
log.Error("%s:\n%s", err, logBuffer)
|
log.Errorf("%s:\n%s", err, logBuffer)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -726,7 +727,7 @@ func (c *Cli) CmdLabels(action string, issue string, labels []string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *Cli) CmdAssign(issue string, user string) error {
|
func (c *Cli) CmdAssign(issue string, user string) error {
|
||||||
log.Debug("assign called")
|
log.Debugf("assign called")
|
||||||
|
|
||||||
json, err := jsonEncode(map[string]interface{}{
|
json, err := jsonEncode(map[string]interface{}{
|
||||||
"name": user,
|
"name": user,
|
||||||
@@ -737,8 +738,8 @@ func (c *Cli) CmdAssign(issue string, user string) error {
|
|||||||
|
|
||||||
uri := fmt.Sprintf("%s/rest/api/2/issue/%s/assignee", c.endpoint, issue)
|
uri := fmt.Sprintf("%s/rest/api/2/issue/%s/assignee", c.endpoint, issue)
|
||||||
if c.getOptBool("dryrun", false) {
|
if c.getOptBool("dryrun", false) {
|
||||||
log.Debug("PUT: %s", json)
|
log.Debugf("PUT: %s", json)
|
||||||
log.Debug("Dryrun mode, skipping PUT")
|
log.Debugf("Dryrun mode, skipping PUT")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
resp, err := c.put(uri, json)
|
resp, err := c.put(uri, json)
|
||||||
@@ -754,7 +755,7 @@ func (c *Cli) CmdAssign(issue string, user string) error {
|
|||||||
logBuffer := bytes.NewBuffer(make([]byte, 0))
|
logBuffer := bytes.NewBuffer(make([]byte, 0))
|
||||||
resp.Write(logBuffer)
|
resp.Write(logBuffer)
|
||||||
err := fmt.Errorf("Unexpected Response From PUT")
|
err := fmt.Errorf("Unexpected Response From PUT")
|
||||||
log.Error("%s:\n%s", err, logBuffer)
|
log.Errorf("%s:\n%s", err, logBuffer)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
@@ -776,11 +777,11 @@ func (c *Cli) CmdExportTemplates() error {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if fh, err := os.OpenFile(templateFile, os.O_WRONLY|os.O_CREATE, 0644); err != nil {
|
if fh, err := os.OpenFile(templateFile, os.O_WRONLY|os.O_CREATE, 0644); err != nil {
|
||||||
log.Error("Failed to open %s for writing: %s", templateFile, err)
|
log.Errorf("Failed to open %s for writing: %s", templateFile, err)
|
||||||
return err
|
return err
|
||||||
} else {
|
} else {
|
||||||
defer fh.Close()
|
defer fh.Close()
|
||||||
log.Notice("Creating %s", templateFile)
|
log.Noticef("Creating %s", templateFile)
|
||||||
fh.Write([]byte(template))
|
fh.Write([]byte(template))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -788,7 +789,7 @@ func (c *Cli) CmdExportTemplates() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *Cli) CmdRequest(uri, content string) (err error) {
|
func (c *Cli) CmdRequest(uri, content string) (err error) {
|
||||||
log.Debug("request called")
|
log.Debugf("request called")
|
||||||
|
|
||||||
if !strings.HasPrefix(uri, "http") {
|
if !strings.HasPrefix(uri, "http") {
|
||||||
uri = fmt.Sprintf("%s%s", c.endpoint, uri)
|
uri = fmt.Sprintf("%s%s", c.endpoint, uri)
|
||||||
|
|||||||
+39
-18
@@ -5,8 +5,8 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"github.com/Netflix-Skunkworks/go-jira"
|
"github.com/Netflix-Skunkworks/go-jira"
|
||||||
"github.com/coryb/optigo"
|
"github.com/coryb/optigo"
|
||||||
"github.com/op/go-logging"
|
|
||||||
"gopkg.in/coryb/yaml.v2"
|
"gopkg.in/coryb/yaml.v2"
|
||||||
|
"gopkg.in/op/go-logging.v1"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
@@ -65,6 +65,9 @@ Usage:
|
|||||||
jira reopen ISSUE [--edit] <Edit Options>
|
jira reopen ISSUE [--edit] <Edit Options>
|
||||||
jira start ISSUE [--edit] <Edit Options>
|
jira start ISSUE [--edit] <Edit Options>
|
||||||
jira stop ISSUE [--edit] <Edit Options>
|
jira stop ISSUE [--edit] <Edit Options>
|
||||||
|
jira todo ISSUE [--edit] <Edit Options>
|
||||||
|
jira done ISSUE [--edit] <Edit Options>
|
||||||
|
jira prog|progress|in-progress [--edit] <Edit Options>
|
||||||
jira comment ISSUE [--noedit] <Edit Options>
|
jira comment ISSUE [--noedit] <Edit Options>
|
||||||
jira (set,add,remove) labels ISSUE [LABEL] ...
|
jira (set,add,remove) labels ISSUE [LABEL] ...
|
||||||
jira take ISSUE
|
jira take ISSUE
|
||||||
@@ -140,6 +143,11 @@ Command Options:
|
|||||||
"reopen": "reopen",
|
"reopen": "reopen",
|
||||||
"start": "start",
|
"start": "start",
|
||||||
"stop": "stop",
|
"stop": "stop",
|
||||||
|
"todo": "todo",
|
||||||
|
"done": "done",
|
||||||
|
"prog": "in-progress",
|
||||||
|
"progress": "in-progress",
|
||||||
|
"in-progress": "in-progress",
|
||||||
"comment": "comment",
|
"comment": "comment",
|
||||||
"label": "labels",
|
"label": "labels",
|
||||||
"labels": "labels",
|
"labels": "labels",
|
||||||
@@ -203,6 +211,7 @@ Command Options:
|
|||||||
"remove": setopt,
|
"remove": setopt,
|
||||||
"r|reporter=s": setopt,
|
"r|reporter=s": setopt,
|
||||||
"f|queryfields=s": setopt,
|
"f|queryfields=s": setopt,
|
||||||
|
"x|expand=s": setopt,
|
||||||
"s|sort=s": setopt,
|
"s|sort=s": setopt,
|
||||||
"l|limit|max_results=i": setopt,
|
"l|limit|max_results=i": setopt,
|
||||||
"o|override=s%": &opts,
|
"o|override=s%": &opts,
|
||||||
@@ -217,7 +226,7 @@ Command Options:
|
|||||||
})
|
})
|
||||||
|
|
||||||
if err := op.ProcessAll(os.Args[1:]); err != nil {
|
if err := op.ProcessAll(os.Args[1:]); err != nil {
|
||||||
log.Error("%s", err)
|
log.Errorf("%s", err)
|
||||||
usage(false)
|
usage(false)
|
||||||
}
|
}
|
||||||
args := op.Args
|
args := op.Args
|
||||||
@@ -258,36 +267,36 @@ Command Options:
|
|||||||
// apply defaults
|
// apply defaults
|
||||||
for k, v := range defaults {
|
for k, v := range defaults {
|
||||||
if _, ok := opts[k]; !ok {
|
if _, ok := opts[k]; !ok {
|
||||||
log.Debug("Setting %q to %#v from defaults", k, v)
|
log.Debugf("Setting %q to %#v from defaults", k, v)
|
||||||
opts[k] = v
|
opts[k] = v
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Debug("opts: %v", opts)
|
log.Debugf("opts: %v", opts)
|
||||||
log.Debug("args: %v", args)
|
log.Debugf("args: %v", args)
|
||||||
|
|
||||||
if _, ok := opts["endpoint"]; !ok {
|
if _, ok := opts["endpoint"]; !ok {
|
||||||
log.Error("endpoint option required. Either use --endpoint or set a endpoint option in your ~/.jira.d/config.yml file")
|
log.Errorf("endpoint option required. Either use --endpoint or set a endpoint option in your ~/.jira.d/config.yml file")
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
c := jira.New(opts)
|
c := jira.New(opts)
|
||||||
|
|
||||||
log.Debug("opts: %s", opts)
|
log.Debugf("opts: %s", opts)
|
||||||
|
|
||||||
setEditing := func(dflt bool) {
|
setEditing := func(dflt bool) {
|
||||||
log.Debug("Default Editing: %t", dflt)
|
log.Debugf("Default Editing: %t", dflt)
|
||||||
if dflt {
|
if dflt {
|
||||||
if val, ok := opts["noedit"].(bool); ok && val {
|
if val, ok := opts["noedit"].(bool); ok && val {
|
||||||
log.Debug("Setting edit = false")
|
log.Debugf("Setting edit = false")
|
||||||
opts["edit"] = false
|
opts["edit"] = false
|
||||||
} else {
|
} else {
|
||||||
log.Debug("Setting edit = true")
|
log.Debugf("Setting edit = true")
|
||||||
opts["edit"] = true
|
opts["edit"] = true
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if _, ok := opts["edit"].(bool); !ok {
|
if _, ok := opts["edit"].(bool); !ok {
|
||||||
log.Debug("Setting edit = %t", dflt)
|
log.Debugf("Setting edit = %t", dflt)
|
||||||
opts["edit"] = dflt
|
opts["edit"] = dflt
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -295,7 +304,7 @@ Command Options:
|
|||||||
|
|
||||||
requireArgs := func(count int) {
|
requireArgs := func(count int) {
|
||||||
if len(args) < count {
|
if len(args) < count {
|
||||||
log.Error("Not enough arguments. %d required, %d provided", count, len(args))
|
log.Errorf("Not enough arguments. %d required, %d provided", count, len(args))
|
||||||
usage(false)
|
usage(false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -389,6 +398,18 @@ Command Options:
|
|||||||
requireArgs(1)
|
requireArgs(1)
|
||||||
setEditing(false)
|
setEditing(false)
|
||||||
err = c.CmdTransition(args[0], "stop")
|
err = c.CmdTransition(args[0], "stop")
|
||||||
|
case "todo":
|
||||||
|
requireArgs(1)
|
||||||
|
setEditing(false)
|
||||||
|
err = c.CmdTransition(args[0], "To Do")
|
||||||
|
case "done":
|
||||||
|
requireArgs(1)
|
||||||
|
setEditing(false)
|
||||||
|
err = c.CmdTransition(args[0], "Done")
|
||||||
|
case "in-progress":
|
||||||
|
requireArgs(1)
|
||||||
|
setEditing(false)
|
||||||
|
err = c.CmdTransition(args[0], "In Progress")
|
||||||
case "comment":
|
case "comment":
|
||||||
requireArgs(1)
|
requireArgs(1)
|
||||||
setEditing(true)
|
setEditing(true)
|
||||||
@@ -446,12 +467,12 @@ Command Options:
|
|||||||
}
|
}
|
||||||
err = c.CmdRequest(args[0], data)
|
err = c.CmdRequest(args[0], data)
|
||||||
default:
|
default:
|
||||||
log.Error("Unknown command %s", command)
|
log.Errorf("Unknown command %s", command)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("%s", err)
|
log.Errorf("%s", err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
os.Exit(0)
|
os.Exit(0)
|
||||||
@@ -459,7 +480,7 @@ Command Options:
|
|||||||
|
|
||||||
func parseYaml(file string, opts map[string]interface{}) {
|
func parseYaml(file string, opts map[string]interface{}) {
|
||||||
if fh, err := ioutil.ReadFile(file); err == nil {
|
if fh, err := ioutil.ReadFile(file); err == nil {
|
||||||
log.Debug("Found Config file: %s", file)
|
log.Debugf("Found Config file: %s", file)
|
||||||
yaml.Unmarshal(fh, &opts)
|
yaml.Unmarshal(fh, &opts)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -499,21 +520,21 @@ func loadConfigs(opts map[string]interface{}) {
|
|||||||
if stat.Mode()&0111 == 0 {
|
if stat.Mode()&0111 == 0 {
|
||||||
parseYaml(file, tmp)
|
parseYaml(file, tmp)
|
||||||
} else {
|
} else {
|
||||||
log.Debug("Found Executable Config file: %s", file)
|
log.Debugf("Found Executable Config file: %s", file)
|
||||||
// it is executable, so run it and try to parse the output
|
// it is executable, so run it and try to parse the output
|
||||||
cmd := exec.Command(file)
|
cmd := exec.Command(file)
|
||||||
stdout := bytes.NewBufferString("")
|
stdout := bytes.NewBufferString("")
|
||||||
cmd.Stdout = stdout
|
cmd.Stdout = stdout
|
||||||
cmd.Stderr = bytes.NewBufferString("")
|
cmd.Stderr = bytes.NewBufferString("")
|
||||||
if err := cmd.Run(); err != nil {
|
if err := cmd.Run(); err != nil {
|
||||||
log.Error("%s is exectuable, but it failed to execute: %s\n%s", file, err, cmd.Stderr)
|
log.Errorf("%s is exectuable, but it failed to execute: %s\n%s", file, err, cmd.Stderr)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
yaml.Unmarshal(stdout.Bytes(), &tmp)
|
yaml.Unmarshal(stdout.Bytes(), &tmp)
|
||||||
}
|
}
|
||||||
for k, v := range tmp {
|
for k, v := range tmp {
|
||||||
if _, ok := opts[k]; !ok {
|
if _, ok := opts[k]; !ok {
|
||||||
log.Debug("Setting %q to %#v from %s", k, v, file)
|
log.Debugf("Setting %q to %#v from %s", k, v, file)
|
||||||
opts[k] = v
|
opts[k] = v
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-2
@@ -65,8 +65,8 @@ fields:
|
|||||||
- name: {{ .name }}{{end}}{{end}}
|
- name: {{ .name }}{{end}}{{end}}
|
||||||
assignee:
|
assignee:
|
||||||
name: {{ if .overrides.assignee }}{{.overrides.assignee}}{{else}}{{if .fields.assignee }}{{ .fields.assignee.name }}{{end}}{{end}}
|
name: {{ if .overrides.assignee }}{{.overrides.assignee}}{{else}}{{if .fields.assignee }}{{ .fields.assignee.name }}{{end}}{{end}}
|
||||||
reporter:
|
# reporter:
|
||||||
name: {{ if .overrides.reporter }}{{ .overrides.reporter }}{{else if .fields.reporter}}{{ .fields.reporter.name }}{{end}}
|
# name: {{ if .overrides.reporter }}{{ .overrides.reporter }}{{else if .fields.reporter}}{{ .fields.reporter.name }}{{end}}
|
||||||
# watchers
|
# watchers
|
||||||
customfield_10110: {{ range .fields.customfield_10110 }}
|
customfield_10110: {{ range .fields.customfield_10110 }}
|
||||||
- name: {{ .name }}{{end}}{{if .overrides.watcher}}
|
- name: {{ .name }}{{end}}{{if .overrides.watcher}}
|
||||||
|
|||||||
@@ -12,35 +12,46 @@ import (
|
|||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
"text/template"
|
"text/template"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func homedir() string {
|
||||||
|
if runtime.GOOS == "windows" {
|
||||||
|
return os.Getenv("USERPROFILE")
|
||||||
|
}
|
||||||
|
return os.Getenv("HOME")
|
||||||
|
}
|
||||||
|
|
||||||
func FindParentPaths(fileName string) []string {
|
func FindParentPaths(fileName string) []string {
|
||||||
cwd, _ := os.Getwd()
|
cwd, _ := os.Getwd()
|
||||||
|
|
||||||
paths := make([]string, 0)
|
paths := make([]string, 0)
|
||||||
|
|
||||||
// special case if homedir is not in current path then check there anyway
|
// special case if homedir is not in current path then check there anyway
|
||||||
homedir := os.Getenv("HOME")
|
homedir := homedir()
|
||||||
if !strings.HasPrefix(cwd, homedir) {
|
if !filepath.HasPrefix(cwd, homedir) {
|
||||||
file := fmt.Sprintf("%s/%s", homedir, fileName)
|
path := filepath.Join(homedir, fileName)
|
||||||
if _, err := os.Stat(file); err == nil {
|
if _, err := os.Stat(path); err == nil {
|
||||||
paths = append(paths, file)
|
paths = append(paths, path)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var dir string
|
path := filepath.Join(cwd, fileName)
|
||||||
for _, part := range strings.Split(cwd, string(os.PathSeparator)) {
|
if _, err := os.Stat(path); err == nil {
|
||||||
if dir == "/" {
|
paths = append(paths, path)
|
||||||
dir = fmt.Sprintf("/%s", part)
|
}
|
||||||
} else {
|
for true {
|
||||||
dir = fmt.Sprintf("%s/%s", dir, part)
|
cwd = filepath.Dir(cwd)
|
||||||
|
path := filepath.Join(cwd, fileName)
|
||||||
|
if _, err := os.Stat(path); err == nil {
|
||||||
|
paths = append(paths, path)
|
||||||
}
|
}
|
||||||
file := fmt.Sprintf("%s/%s", dir, fileName)
|
if cwd[len(cwd)-1] == filepath.Separator {
|
||||||
if _, err := os.Stat(file); err == nil {
|
break
|
||||||
paths = append(paths, file)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return paths
|
return paths
|
||||||
@@ -57,8 +68,9 @@ func FindClosestParentPath(fileName string) (string, error) {
|
|||||||
func readFile(file string) string {
|
func readFile(file string) string {
|
||||||
var bytes []byte
|
var bytes []byte
|
||||||
var err error
|
var err error
|
||||||
|
log.Debugf("readFile: reading %q", file)
|
||||||
if bytes, err = ioutil.ReadFile(file); err != nil {
|
if bytes, err = ioutil.ReadFile(file); err != nil {
|
||||||
log.Error("Failed to read file %s: %s", file, err)
|
log.Errorf("Failed to read file %s: %s", file, err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
return string(bytes)
|
return string(bytes)
|
||||||
@@ -195,11 +207,11 @@ func runTemplate(templateContent string, data interface{}, out io.Writer) error
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
if tmpl, err := template.New("template").Funcs(funcs).Parse(templateContent); err != nil {
|
if tmpl, err := template.New("template").Funcs(funcs).Parse(templateContent); err != nil {
|
||||||
log.Error("Failed to parse template: %s", err)
|
log.Errorf("Failed to parse template: %s", err)
|
||||||
return err
|
return err
|
||||||
} else {
|
} else {
|
||||||
if err := tmpl.Execute(out, data); err != nil {
|
if err := tmpl.Execute(out, data); err != nil {
|
||||||
log.Error("Failed to execute template: %s", err)
|
log.Errorf("Failed to execute template: %s", err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -215,7 +227,7 @@ func responseToJson(resp *http.Response, err error) (interface{}, error) {
|
|||||||
if resp.StatusCode == 400 {
|
if resp.StatusCode == 400 {
|
||||||
if val, ok := data.(map[string]interface{})["errorMessages"]; ok {
|
if val, ok := data.(map[string]interface{})["errorMessages"]; ok {
|
||||||
for _, errMsg := range val.([]interface{}) {
|
for _, errMsg := range val.([]interface{}) {
|
||||||
log.Error("%s", errMsg)
|
log.Errorf("%s", errMsg)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -228,7 +240,7 @@ func jsonDecode(io io.Reader) interface{} {
|
|||||||
var data interface{}
|
var data interface{}
|
||||||
err = json.Unmarshal(content, &data)
|
err = json.Unmarshal(content, &data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("JSON Parse Error: %s from %s", err, content)
|
log.Errorf("JSON Parse Error: %s from %s", err, content)
|
||||||
}
|
}
|
||||||
return data
|
return data
|
||||||
}
|
}
|
||||||
@@ -239,7 +251,7 @@ func jsonEncode(data interface{}) (string, error) {
|
|||||||
|
|
||||||
err := enc.Encode(data)
|
err := enc.Encode(data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("Failed to encode data %s: %s", data, err)
|
log.Errorf("Failed to encode data %s: %s", data, err)
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
return buffer.String(), nil
|
return buffer.String(), nil
|
||||||
@@ -249,7 +261,7 @@ func jsonWrite(file string, data interface{}) {
|
|||||||
fh, err := os.OpenFile(file, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
|
fh, err := os.OpenFile(file, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
|
||||||
defer fh.Close()
|
defer fh.Close()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("Failed to open %s: %s", file, err)
|
log.Errorf("Failed to open %s: %s", file, err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
enc := json.NewEncoder(fh)
|
enc := json.NewEncoder(fh)
|
||||||
@@ -260,11 +272,11 @@ func yamlWrite(file string, data interface{}) {
|
|||||||
fh, err := os.OpenFile(file, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
|
fh, err := os.OpenFile(file, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
|
||||||
defer fh.Close()
|
defer fh.Close()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("Failed to open %s: %s", file, err)
|
log.Errorf("Failed to open %s: %s", file, err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
if out, err := yaml.Marshal(data); err != nil {
|
if out, err := yaml.Marshal(data); err != nil {
|
||||||
log.Error("Failed to marshal yaml %v: %s", data, err)
|
log.Errorf("Failed to marshal yaml %v: %s", data, err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
} else {
|
} else {
|
||||||
fh.Write(out)
|
fh.Write(out)
|
||||||
@@ -306,7 +318,7 @@ func yamlFixup(data interface{}) (interface{}, error) {
|
|||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
err := fmt.Errorf("YAML: key %s is type '%T', require 'string'", key, k)
|
err := fmt.Errorf("YAML: key %s is type '%T', require 'string'", key, k)
|
||||||
log.Error("%s", err)
|
log.Errorf("%s", err)
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -343,16 +355,16 @@ func yamlFixup(data interface{}) (interface{}, error) {
|
|||||||
|
|
||||||
func mkdir(dir string) error {
|
func mkdir(dir string) error {
|
||||||
if stat, err := os.Stat(dir); err != nil && !os.IsNotExist(err) {
|
if stat, err := os.Stat(dir); err != nil && !os.IsNotExist(err) {
|
||||||
log.Error("Failed to stat %s: %s", dir, err)
|
log.Errorf("Failed to stat %s: %s", dir, err)
|
||||||
return err
|
return err
|
||||||
} else if err == nil && !stat.IsDir() {
|
} else if err == nil && !stat.IsDir() {
|
||||||
err := fmt.Errorf("%s exists and is not a directory!", dir)
|
err := fmt.Errorf("%s exists and is not a directory!", dir)
|
||||||
log.Error("%s", err)
|
log.Errorf("%s", err)
|
||||||
return err
|
return err
|
||||||
} else {
|
} else {
|
||||||
// dir does not exist, so try to create it
|
// dir does not exist, so try to create it
|
||||||
if err := os.MkdirAll(dir, 0755); err != nil {
|
if err := os.MkdirAll(dir, 0755); err != nil {
|
||||||
log.Error("Failed to mkdir -p %s: %s", dir, err)
|
log.Errorf("Failed to mkdir -p %s: %s", dir, err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user