0
0
mirror of https://github.com/thegeeklab/github-releases-notifier.git synced 2024-11-13 01:30:40 +00:00

Add all Go source files

This commit is contained in:
Matthias Loibl 2017-08-08 12:08:16 +02:00
parent c3bcf09882
commit 19b8b3382e
No known key found for this signature in database
GPG Key ID: B1C7DF661ABB2C1A
5 changed files with 305 additions and 0 deletions

80
main.go Normal file
View File

@ -0,0 +1,80 @@
package main
import (
"context"
"os"
"strings"
"time"
"github.com/alexflint/go-arg"
"github.com/go-kit/kit/log"
"github.com/go-kit/kit/log/level"
"github.com/joho/godotenv"
"github.com/shurcooL/githubql"
"golang.org/x/oauth2"
)
// Config of env and args
type Config struct {
GithubToken string `arg:"env:GITHUB_TOKEN"`
Interval time.Duration `arg:"env:INTERVAL"`
LogLevel string `arg:"env:LOG_LEVEL"`
Repositories []string `arg:"-r,separate"`
SlackHook string `arg:"env:SLACK_HOOK"`
}
// Token returns an oauth2 token or an error.
func (c Config) Token() *oauth2.Token {
return &oauth2.Token{AccessToken: c.GithubToken}
}
func main() {
_ = godotenv.Load()
c := Config{
Interval: time.Hour,
LogLevel: "info",
}
arg.MustParse(&c)
logger := log.NewJSONLogger(log.NewSyncWriter(os.Stdout))
logger = log.With(logger,
"ts", log.DefaultTimestampUTC,
"caller", log.Caller(5),
)
level.SetKey("severity")
switch strings.ToLower(c.LogLevel) {
case "debug":
logger = level.NewFilter(logger, level.AllowDebug())
case "warn":
logger = level.NewFilter(logger, level.AllowWarn())
case "error":
logger = level.NewFilter(logger, level.AllowError())
default:
logger = level.NewFilter(logger, level.AllowInfo())
}
tokenSource := oauth2.StaticTokenSource(c.Token())
client := oauth2.NewClient(context.Background(), tokenSource)
checker := &Checker{
logger: logger,
client: githubql.NewClient(client),
}
releases := make(chan Repository)
go checker.Run(c.Interval, c.Repositories, releases)
slack := SlackSender{Hook: c.SlackHook}
level.Info(logger).Log("msg", "waiting for new releases")
for repository := range releases {
if err := slack.Send(repository); err != nil {
level.Warn(logger).Log(
"msg", "failed to send release to messenger",
"err", err,
)
continue
}
}
}

15
release.go Normal file
View File

@ -0,0 +1,15 @@
package main
import (
"net/url"
"time"
)
// Release of a repository tagged via GitHub.
type Release struct {
ID string
Name string
Description string
URL url.URL
PublishedAt time.Time
}

135
releasechecker.go Normal file
View File

@ -0,0 +1,135 @@
package main
import (
"context"
"fmt"
"strings"
"time"
"github.com/go-kit/kit/log"
"github.com/go-kit/kit/log/level"
"github.com/shurcooL/githubql"
)
// Checker has a githubql client to run queries and also knows about
// the current repositories releases to compare against.
type Checker struct {
logger log.Logger
client *githubql.Client
releases map[string]Repository
}
// Run the queries and comparisons for the given repositories in a given interval.
func (c *Checker) Run(interval time.Duration, repositories []string, releases chan<- Repository) {
if c.releases == nil {
c.releases = make(map[string]Repository)
}
for {
for _, repoName := range repositories {
s := strings.Split(repoName, "/")
owner, name := s[0], s[1]
nextRepo, err := c.query(owner, name)
if err != nil {
level.Warn(c.logger).Log(
"msg", "failed to query the repository's releases",
"owner", owner,
"name", name,
"err", err,
)
continue
}
// For debugging uncomment this next line
//releases <- nextRepo
currRepo, ok := c.releases[repoName]
// We've queried the repository for the first time.
// Saving the current state to compare with the next iteration.
if !ok {
c.releases[repoName] = nextRepo
continue
}
if nextRepo.Release.PublishedAt.After(currRepo.Release.PublishedAt) {
releases <- nextRepo
c.releases[repoName] = nextRepo
} else {
level.Debug(c.logger).Log(
"msg", "no new release for repository",
"owner", owner,
"name", name,
)
}
}
time.Sleep(interval)
}
}
// This should be improved in the future to make batch requests for all watched repositories at once
// TODO: https://github.com/shurcooL/githubql/issues/17
func (c *Checker) query(owner, name string) (Repository, error) {
var query struct {
Repository struct {
ID githubql.ID
Name githubql.String
Description githubql.String
URL githubql.URI
Releases struct {
Edges []struct {
Node struct {
ID githubql.ID
Name githubql.String
Description githubql.String
URL githubql.URI
PublishedAt githubql.DateTime
}
}
} `graphql:"releases(last: 1)"`
} `graphql:"repository(owner: $owner, name: $name)"`
}
variables := map[string]interface{}{
"owner": githubql.String(owner),
"name": githubql.String(name),
}
if err := c.client.Query(context.Background(), &query, variables); err != nil {
return Repository{}, err
}
repositoryID, ok := query.Repository.ID.(string)
if !ok {
return Repository{}, fmt.Errorf("can't convert repository id to string: %v", query.Repository.ID)
}
if len(query.Repository.Releases.Edges) == 0 {
return Repository{}, fmt.Errorf("can't find any releases for %s/%s", owner, name)
}
latestRelease := query.Repository.Releases.Edges[0].Node
releaseID, ok := latestRelease.ID.(string)
if !ok {
return Repository{}, fmt.Errorf("can't convert release id to string: %v", query.Repository.ID)
}
return Repository{
ID: repositoryID,
Name: string(query.Repository.Name),
Owner: owner,
Description: string(query.Repository.Description),
URL: *query.Repository.URL.URL,
Release: Release{
ID: releaseID,
Name: string(latestRelease.Name),
Description: string(latestRelease.Description),
URL: *latestRelease.URL.URL,
PublishedAt: latestRelease.PublishedAt.Time,
},
}, nil
}

13
repository.go Normal file
View File

@ -0,0 +1,13 @@
package main
import "net/url"
// Repository on GitHub.
type Repository struct {
ID string
Name string
Owner string
Description string
URL url.URL
Release Release
}

62
slack.go Normal file
View File

@ -0,0 +1,62 @@
package main
import (
"bytes"
"context"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"time"
)
type SlackSender struct {
Hook string
}
type slackPayload struct {
Username string `json:"username"`
IconEmoji string `json:"icon_emoji"`
Text string `json:"text"`
}
func (s *SlackSender) Send(repository Repository) error {
payload := slackPayload{
Username: "GitHub Releases",
IconEmoji: ":github:",
Text: fmt.Sprintf(
"<%s|%s/%s>: <%s|%s> released",
repository.URL.String(),
repository.Owner,
repository.Name,
repository.Release.URL.String(),
repository.Release.Name,
),
}
payloadData, err := json.Marshal(payload)
if err != nil {
return err
}
req, err := http.NewRequest(http.MethodPost, s.Hook, bytes.NewReader(payloadData))
if err != nil {
return err
}
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Millisecond)
defer cancel()
req.WithContext(ctx)
resp, err := http.DefaultClient.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
body, _ := ioutil.ReadAll(resp.Body)
return fmt.Errorf("request didn't respond with 200 OK: %s, %s", resp.Status, body)
}
return nil
}