Hi folks.

So, I was playing around and created a client for JIRA written in Go. It was nice to do some JSON transformation. And sending POSTS was really trivial.

It’s still in it’s infancy and I have a couple of more features I want to implement, but, here is the code.


package main

import (
	"bytes"
	"encoding/json"
	"flag"
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
	"os"

	"github.com/BurntSushi/toml"
)

var configFile = "~/.jira_config.toml"
var parameter string

var flags struct {
	Comment     string
	Description string
	IssueKey    string
	Priority    string
	Resolution  string
	Title       string
	Project     string
}

//Issue is a representation of a Jira Issue
type Issue struct {
	Fields struct {
		Project struct {
			Key string `json:"key"`
		} `json:"project"`
		Summary     string `json:"summary"`
		Description string `json:"description"`
		Issuetype   struct {
			Name string `json:"name"`
		} `json:"issuetype"`
		Priority struct {
			ID string `json:"id"`
		} `json:"priority"`
	} `json:"fields"`
}

//Transition defines a transition json object. Used for starting, stoppinp
//generally for state stranfer
type Transition struct {
	Fields struct {
		Resolution struct {
			Name string `json:"name"`
		} `json:"resolution"`
	} `json:"fields"`
	Transition struct {
		ID string `json:"id"`
	} `json:"transition"`
}

//Credentials a representation of a JIRA config which helds API permissions
type Credentials struct {
	Username string
	Password string
	URL      string
}

func init() {
	flag.StringVar(&flags.Comment, "m", "Default Comment", "A Comment when changing the status of an Issue.")
	flag.StringVar(&flags.Description, "d", "Default Description", "Provide a description for a newly created Issue.")
	flag.StringVar(&flags.Priority, "p", "2", "The priority of an Issue which will be set.")
	flag.StringVar(&flags.IssueKey, "k", "", "Issue key of an issue.")
	flag.StringVar(&flags.Resolution, "r", "Done", "Resolution when an issue is closed. Ex.: Done, Fixed, Won't fix.")
	flag.StringVar(&flags.Title, "t", "Default Title", "Title of an Issue.")
	flag.StringVar(&flags.Project, "o", "IT", "Define a Project to create a ticket in.")
	flag.Parse()
}

func (cred *Credentials) initConfig() {
	if _, err := os.Stat(configFile); err != nil {
		log.Fatalf("Error using config file: %v", err)
	}

	if _, err := toml.DecodeFile(configFile, cred); err != nil {
		log.Fatal("Error during decoding toml config: ", err)
	}
}

func main() {
	if len(flag.Args()) < 1 {
		log.Fatal("Please provide an action to take. Usage information:")
	}
	parameter = flag.Arg()
	switch parameter {
	case "close":
		closeIssue(flags.IssueKey)
	case "start":
		startIssue(flags.IssueKey)
	case "create":
		createIssue()
	}
}

func closeIssue(issueKey string) {
	if issueKey == "" {
		log.Fatal("Please provide an issueID with -k")
	}
	fmt.Println("Closing issue number: ", issueKey)

	var trans Transition

	//TODO: Add the ability to define a comment for the close reason
	trans.Fields.Resolution.Name = flags.Resolution
	trans.Transition.ID = "2"
	marhsalledTrans, err := json.Marshal(trans)
	if err != nil {
		log.Fatal("Error occured when marshaling transition: ", err)
	}
	fmt.Println("Marshalled:", trans)
	sendRequest(marhsalledTrans, "POST", issueKey+"/transitions?expand=transitions.fields")
}

func startIssue(issueID string) {
	if issueID == "" {
		log.Fatal("Please provide an issueID with -i")
	}

	fmt.Println("Starting issue number:", issueID)
}

func createIssue() {
	fmt.Println("Creating new issue.")
	var issue Issue
	issue.Fields.Description = flags.Description
	issue.Fields.Priority.ID = flags.Priority
	issue.Fields.Summary = flags.Title
	issue.Fields.Project.Key = flags.Project
	issue.Fields.Issuetype.Name = "Task"
	marshalledIssue, err := json.Marshal(issue)
	if err != nil {
		log.Fatal("Error occured when Marshaling Issue:", err)
	}
	sendRequest(marshalledIssue, "POST", "")
}

func sendRequest(jsonStr []byte, method string, url string) {
	cred := &Credentials{}
	cred.initConfig()
	fmt.Println("Json:", string(jsonStr))
	req, err := http.NewRequest(method, cred.URL+url, bytes.NewBuffer(jsonStr))
	req.Header.Set("Content-Type", "application/json")
	req.SetBasicAuth(cred.Username, cred.Password)

	client := &http.Client{}
	resp, err := client.Do(req)
	if err != nil {
		panic(err)
	}
	defer resp.Body.Close()

	fmt.Println("response Status:", resp.Status)
	fmt.Println("response Headers:", resp.Header)
	body, _ := ioutil.ReadAll(resp.Body)
	fmt.Println("response Body:", string(body))

}

It can also be found under my github page: GoJira Github.

Feel free to open up issues if you would like to use it and need some features which you would find interesting. Currently the username and password for the API are stored in a local config file in your home folder. Later on, I’ll add the ability to have a token rather than a username:password combination.

Thanks for reading!

Gergely.