From a56501902779eceabc31053e97a6019957e11c93 Mon Sep 17 00:00:00 2001 From: Nicolas Date: Wed, 20 Jul 2022 16:04:05 +0200 Subject: [PATCH] Initial poc --- README.md | 16 ++++++++ go.mod | 13 ++++++ go.sum | 18 ++++++++ main.go | 120 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 167 insertions(+) create mode 100644 README.md create mode 100644 go.mod create mode 100644 go.sum create mode 100644 main.go diff --git a/README.md b/README.md new file mode 100644 index 0000000..efdf012 --- /dev/null +++ b/README.md @@ -0,0 +1,16 @@ +# jelease - A newreleases.io ➡️ Jira connector + +Automatically create Jira tickets when a newreleases.io release is detected using webhooks. + +## Configuration: + +The application requires the following environment variables to be set: +- `PORT`: The port the application is expecting traffic on +- `JIRA_URL`: The URL of your Jira instance +- `JIRA_USER`: Jira username to authenticate API requests +- `JIRA_TOKEN`: Jira API token, can also be a password in self-hosted instances +- `JIRA_PROJECT`: Jira Project key the tickets will be created in + +## Usage + +Send newreleases.io webhooks to the `host:port/webhook` route. \ No newline at end of file diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..cac52a7 --- /dev/null +++ b/go.mod @@ -0,0 +1,13 @@ +module github.2rioffice.com/nicolas-mohr/jilease + +go 1.18 + +require github.com/andygrunwald/go-jira v1.15.1 + +require ( + github.com/fatih/structs v1.1.0 // indirect + github.com/golang-jwt/jwt/v4 v4.3.0 // indirect + github.com/google/go-querystring v1.1.0 // indirect + github.com/pkg/errors v0.9.1 // indirect + github.com/trivago/tgo v1.0.7 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..c22c927 --- /dev/null +++ b/go.sum @@ -0,0 +1,18 @@ +github.com/andygrunwald/go-jira v1.15.1 h1:6J9aYKb9sW8bxv3pBLYBrs0wdsFrmGI5IeTgWSKWKc8= +github.com/andygrunwald/go-jira v1.15.1/go.mod h1:GIYN1sHOIsENWUZ7B4pDeT/nxEtrZpE8l0987O67ZR8= +github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo= +github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= +github.com/golang-jwt/jwt/v4 v4.3.0 h1:kHL1vqdqWNfATmA0FNMdmZNMyZI1U6O31X4rlIPoBog= +github.com/golang-jwt/jwt/v4 v4.3.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg= +github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.7 h1:81/ik6ipDQS2aGcBfIN5dHDB36BwrStyeAQquSYCV4o= +github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= +github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= +github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/trivago/tgo v1.0.7 h1:uaWH/XIy9aWYWpjm2CU3RpcqZXmX2ysQ9/Go+d9gyrM= +github.com/trivago/tgo v1.0.7/go.mod h1:w4dpD+3tzNIIiIfkWWa85w5/B77tlvdZckQ+6PkFnhc= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/main.go b/main.go new file mode 100644 index 0000000..e9e5d1f --- /dev/null +++ b/main.go @@ -0,0 +1,120 @@ +package main + +import ( + "encoding/json" + "errors" + "fmt" + "io" + "io/ioutil" + "net/http" + "os" + + jira "github.com/andygrunwald/go-jira" +) + +const ( + defaultPort = "8080" +) + +var ( + jiraClient *jira.Client + jiraProject string +) + +// Some fields omitted for simplicity, check out the documentation at https://newreleases.io/webhooks +type NewRelease struct { + Provider string `json:"provider"` + Project string `json:"project"` + Version string `json:"version"` +} + +func ReleaseToJiraIssue(newRelease NewRelease) jira.Issue { + return jira.Issue{ + Fields: &jira.IssueFields{ + Description: "Update issue generated by newreleases.io", + Project: jira.Project{ + Key: jiraProject, + }, + Type: jira.IssueType{ + Name: "Task", + }, + Summary: fmt.Sprintf("Update %v to version %v", newRelease.Project, newRelease.Version), + }, + } +} + +func getRoot(w http.ResponseWriter, r *http.Request) { + fmt.Printf("got / request\n") + io.WriteString(w, "Pong!\n") +} + +func postWebhook(w http.ResponseWriter, r *http.Request) { + if r.Method != http.MethodPost { + http.Error(w, http.StatusText(http.StatusMethodNotAllowed), http.StatusMethodNotAllowed) + } + // parse newreleases.io webhook + decoder := json.NewDecoder(r.Body) + var newRelease NewRelease + err := decoder.Decode(&newRelease) + if err != nil { + http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest) + fmt.Fprintf(os.Stderr, "Couldn't decode body to json: %v\n error: %v\n", r.Body, err) + } + fmt.Println(newRelease) + + // create jira ticket + i := ReleaseToJiraIssue(newRelease) + issue, response, err := jiraClient.Issue.Create(&i) + if err != nil { + fmt.Printf("%+v\n", err) + body, _ := ioutil.ReadAll(response.Body) + fmt.Printf("Error response from Jira: %+v\n", string(body)) + http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) + + } + fmt.Printf("Created issue %v\n", issue.ID) +} + +func main() { + // jira setup + jiraUrl, jiraUrlPresent := os.LookupEnv("JIRA_URL") + jiraUser, jiraUserPresent := os.LookupEnv("JIRA_USER") + jiraToken, jiraTokenPresent := os.LookupEnv("JIRA_TOKEN") + var jiraProjectPresent bool + jiraProject, jiraProjectPresent = os.LookupEnv("JIRA_PROJECT") + if !(jiraUserPresent && jiraTokenPresent && jiraUrlPresent && jiraProjectPresent) { + fmt.Printf("Missing jira environment variables.\nJIRA_URL present: %v\nJIRA_USER present: %v\nJIRA_TOKEN present: %v\nJIRA_PROJECT present: %v\n", + jiraUrlPresent, jiraTokenPresent, jiraUserPresent, jiraProjectPresent) + return + } + + fmt.Printf("Jira URL: %v\n", jiraUrl) + tp := jira.BasicAuthTransport{ + Username: jiraUser, + Password: jiraToken, + } + + var err error + jiraClient, err = jira.NewClient(tp.Client(), jiraUrl) + if err != nil { + panic(err) + } + + // http server setup + port, portPresent := os.LookupEnv("PORT") + if !portPresent { + port = defaultPort + fmt.Printf("No port specified, defaulting to %v\n", defaultPort) + } + + http.HandleFunc("/webhook", postWebhook) + http.HandleFunc("/", getRoot) + fmt.Printf("Listening on port %v\n", port) + err = http.ListenAndServe(fmt.Sprintf(":%v", port), nil) + if errors.Is(err, http.ErrServerClosed) { + fmt.Printf("server closed\n") + } else if err != nil { + fmt.Printf("error starting server: %s\n", err) + os.Exit(1) + } +}