From 994ddf0422111b8841d6682a60c1f31791f721f1 Mon Sep 17 00:00:00 2001 From: Michael Cordell Date: Sat, 9 Jun 2018 07:32:28 -0700 Subject: [PATCH] Add circleci module Needs a Circle API token, which can be found at https://circleci.com/account/api. This is passed under the environmental variable WTF_CIRCLE_API_KEY. --- circleci/build.go | 10 ++++++ circleci/client.go | 80 ++++++++++++++++++++++++++++++++++++++++++++ circleci/widget.go | 83 ++++++++++++++++++++++++++++++++++++++++++++++ wtf.go | 4 +++ 4 files changed, 177 insertions(+) create mode 100644 circleci/build.go create mode 100644 circleci/client.go create mode 100644 circleci/widget.go diff --git a/circleci/build.go b/circleci/build.go new file mode 100644 index 00000000..f0ee6cab --- /dev/null +++ b/circleci/build.go @@ -0,0 +1,10 @@ +package circleci + +type Build struct { + AuthorEmail string `json:"author_email"` + AuthorName string `json:"author_name"` + Branch string `json:"branch"` + BuildNum int `json:"build_num"` + Reponame string `json:"reponame"` + Status string `json:"status"` +} diff --git a/circleci/client.go b/circleci/client.go new file mode 100644 index 00000000..16d7eb82 --- /dev/null +++ b/circleci/client.go @@ -0,0 +1,80 @@ +package circleci + +import ( + "bytes" + "encoding/json" + "fmt" + "io" + "io/ioutil" + "net/http" + "net/url" + "os" +) + +const APIEnvKey = "WTF_CIRCLE_API_KEY" + +func BuildsFor() ([]*Build, error) { + builds := []*Build{} + + resp, err := circleRequest("recent-builds") + if err != nil { + return builds, err + } + + parseJson(&builds, resp.Body) + + return builds, nil +} + +/* -------------------- Unexported Functions -------------------- */ + +var ( + circleAPIURL = &url.URL{Scheme: "https", Host: "circleci.com", Path: "/api/v1/"} +) + +func circleRequest(path string) (*http.Response, error) { + params := url.Values{} + params.Add("circle-token", apiKey()) + + url := circleAPIURL.ResolveReference(&url.URL{Path: path, RawQuery: params.Encode()}) + + req, err := http.NewRequest("GET", url.String(), nil) + req.Header.Add("Accept", "application/json") + req.Header.Add("Content-Type", "application/json") + if err != nil { + return nil, err + } + + httpClient := &http.Client{} + resp, err := httpClient.Do(req) + if err != nil { + return nil, err + } + + if resp.StatusCode < 200 || resp.StatusCode > 299 { + return nil, fmt.Errorf(resp.Status) + } + + return resp, nil +} + +func apiKey() string { + return os.Getenv(APIEnvKey) +} + +func parseJson(obj interface{}, text io.Reader) { + jsonStream, err := ioutil.ReadAll(text) + if err != nil { + panic(err) + } + + decoder := json.NewDecoder(bytes.NewReader(jsonStream)) + + for { + if err := decoder.Decode(obj); err == io.EOF { + break + } else if err != nil { + panic(err) + } + } +} diff --git a/circleci/widget.go b/circleci/widget.go new file mode 100644 index 00000000..0e3d6a28 --- /dev/null +++ b/circleci/widget.go @@ -0,0 +1,83 @@ +package circleci + +import ( + "fmt" + "github.com/olebedev/config" + "github.com/senorprogrammer/wtf/wtf" +) + +// Config is a pointer to the global config object +var Config *config.Config + +type Widget struct { + wtf.TextWidget +} + +func NewWidget() *Widget { + widget := Widget{ + TextWidget: wtf.NewTextWidget(" CircleCI ", "circleci", false), + } + + return &widget +} + +/* -------------------- Exported Functions -------------------- */ + +func (widget *Widget) Refresh() { + if widget.Disabled() { + return + } + + builds, err := BuildsFor() + + widget.UpdateRefreshedAt() + + widget.View.SetTitle(fmt.Sprintf("%s - Builds", widget.Name)) + + if err != nil { + widget.View.SetWrap(true) + fmt.Fprintf(widget.View, "%v", err) + } else { + widget.View.SetWrap(false) + widget.View.SetText(fmt.Sprintf("%s", widget.contentFrom(builds))) + } +} + +/* -------------------- Unexported Functions -------------------- */ + +func (widget *Widget) contentFrom(builds []*Build) string { + var str string + for idx, build := range builds { + if idx > 10 { + return str + } + + str = str + fmt.Sprintf( + "[%s] %s-%d (%s) [white]%s\n", + buildColor(build), + build.Reponame, + build.BuildNum, + build.Branch, + build.AuthorName, + ) + } + + return str +} + +func buildColor(b *Build) string { + var color string + + switch b.Status { + case "failed": + color = "red" + case "running": + color = "yellow" + case "success": + color = "green" + default: + color = "white" + } + + return color +} diff --git a/wtf.go b/wtf.go index 2c27fb13..7440fe09 100644 --- a/wtf.go +++ b/wtf.go @@ -13,6 +13,7 @@ import ( "github.com/senorprogrammer/wtf/bamboohr" "github.com/senorprogrammer/wtf/bargraph" "github.com/senorprogrammer/wtf/cfg" + "github.com/senorprogrammer/wtf/circleci" "github.com/senorprogrammer/wtf/clocks" "github.com/senorprogrammer/wtf/cmdrunner" "github.com/senorprogrammer/wtf/cryptoexchanges/bittrex" @@ -175,6 +176,8 @@ func addWidget(app *tview.Application, pages *tview.Pages, widgetName string) { Widgets = append(Widgets, bargraph.NewWidget()) case "bittrex": Widgets = append(Widgets, bittrex.NewWidget()) + case "circleci": + Widgets = append(Widgets, circleci.NewWidget()) case "clocks": Widgets = append(Widgets, clocks.NewWidget()) case "cmdrunner": @@ -224,6 +227,7 @@ func makeWidgets(app *tview.Application, pages *tview.Pages) { bamboohr.Config = Config bargraph.Config = Config bittrex.Config = Config + circleci.Config = Config clocks.Config = Config cmdrunner.Config = Config cryptolive.Config = Config