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/ipapi/widget.go b/ipapi/widget.go new file mode 100644 index 00000000..d65f2456 --- /dev/null +++ b/ipapi/widget.go @@ -0,0 +1,131 @@ +package ipapi + +import ( + "encoding/json" + "fmt" + "net/http" + "strconv" + "text/template" + + "bytes" + + "github.com/olebedev/config" + "github.com/senorprogrammer/wtf/wtf" +) + +// Config is a pointer to the global config object +var Config *config.Config + +// Widget widget struct +type Widget struct { + wtf.TextWidget + result string + colors struct { + name, value string + } +} + +type ipinfo struct { + Query string `json:"query"` + ISP string `json:"isp"` + AS string `json:"as"` + City string `json:"city"` + Region string `json:"region"` + Country string `json:"country"` + CountryCode string `json:"countryCode"` + Latitude float64 `json:"lat"` + Longitude float64 `json:"lon"` + PostalCode string `json:"zip"` + Organization string `json:"org"` + Timezone string `json:"timezone"` +} + +// NewWidget constructor +func NewWidget() *Widget { + widget := Widget{ + TextWidget: wtf.NewTextWidget(" IPInfo ", "ipapi", false), + } + + widget.View.SetWrap(false) + + widget.config() + + return &widget +} + +// Refresh refresh the module +func (widget *Widget) Refresh() { + widget.UpdateRefreshedAt() + widget.ipinfo() + widget.View.SetText(widget.result) +} + +//this method reads the config and calls ipinfo for ip information +func (widget *Widget) ipinfo() { + client := &http.Client{} + req, err := http.NewRequest("GET", "http://ip-api.com/json", nil) + if err != nil { + widget.result = fmt.Sprintf("%s", err.Error()) + return + } + req.Header.Set("User-Agent", "curl") + response, err := client.Do(req) + if err != nil { + widget.result = fmt.Sprintf("%s", err.Error()) + return + } + defer response.Body.Close() + var info ipinfo + err = json.NewDecoder(response.Body).Decode(&info) + if err != nil { + widget.result = fmt.Sprintf("%s", err.Error()) + return + } + + widget.setResult(&info) +} + +// read module configs +func (widget *Widget) config() { + nameColor, valueColor := Config.UString("wtf.mods.ipinfo.colors.name", "red"), Config.UString("wtf.mods.ipinfo.colors.value", "white") + widget.colors.name = nameColor + widget.colors.value = valueColor +} + +func (widget *Widget) setResult(info *ipinfo) { + resultTemplate, _ := template.New("ipinfo_result").Parse( + formatableText("IP Address", "Ip") + + formatableText("ISP", "ISP") + + formatableText("AS", "AS") + + formatableText("City", "City") + + formatableText("Region", "Region") + + formatableText("Country", "Country") + + formatableText("Coordinates", "Coordinates") + + formatableText("Postal Code", "PostalCode") + + formatableText("Organization", "Organization") + + formatableText("Timezone", "Timezone"), + ) + + resultBuffer := new(bytes.Buffer) + + resultTemplate.Execute(resultBuffer, map[string]string{ + "nameColor": widget.colors.name, + "valueColor": widget.colors.value, + "Ip": info.Query, + "ISP": info.ISP, + "AS": info.AS, + "City": info.City, + "Region": info.Region, + "Country": info.Country, + "Coordinates": strconv.FormatFloat(info.Latitude, 'f', 6, 64) + "," + strconv.FormatFloat(info.Longitude, 'f', 6, 64), + "PostalCode": info.PostalCode, + "Organization": info.Organization, + "Timezone": info.Timezone, + }) + + widget.result = resultBuffer.String() +} + +func formatableText(key, value string) string { + return fmt.Sprintf(" [{{.nameColor}}]%s: [{{.valueColor}}]{{.%s}}\n", key, value) +} diff --git a/wtf.go b/wtf.go index 5822eb6b..603e5884 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" @@ -23,7 +24,8 @@ import ( "github.com/senorprogrammer/wtf/gspreadsheets" "github.com/senorprogrammer/wtf/help" "github.com/senorprogrammer/wtf/ipinfo" - "github.com/senorprogrammer/wtf/jenkins" + "github.com/senorprogrammer/wtf/ipapi" + "github.com/senorprogrammer/wtf/jenkins" "github.com/senorprogrammer/wtf/jira" "github.com/senorprogrammer/wtf/newrelic" "github.com/senorprogrammer/wtf/opsgenie" @@ -176,6 +178,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": @@ -192,6 +196,8 @@ func addWidget(app *tview.Application, pages *tview.Pages, widgetName string) { Widgets = append(Widgets, gspreadsheets.NewWidget()) case "ipinfo": Widgets = append(Widgets, ipinfo.NewWidget()) + case "ipapi": + Widgets = append(Widgets, ipapi.NewWidget()) case "jenkins": Widgets = append(Widgets, jenkins.NewWidget()) case "jira": @@ -227,6 +233,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 @@ -235,7 +242,8 @@ func makeWidgets(app *tview.Application, pages *tview.Pages) { github.Config = Config gspreadsheets.Config = Config ipinfo.Config = Config - jenkins.Config = Config + ipapi.Config = Config + jenkins.Config = Config jira.Config = Config newrelic.Config = Config opsgenie.Config = Config