├── .env.example ├── .gitignore ├── .travis.yml ├── Dockerfile ├── LICENSE ├── Makefile ├── README.md ├── VERSION ├── deployments ├── docker-compose.yml └── kubernetes.yml ├── go.mod ├── go.sum ├── main.go ├── release.go ├── releasechecker.go ├── repository.go ├── screenshot.png ├── slack.go └── vendor ├── github.com ├── alexflint │ ├── go-arg │ │ ├── .gitignore │ │ ├── .travis.yml │ │ ├── LICENSE │ │ ├── README.md │ │ ├── doc.go │ │ ├── go.mod │ │ ├── go.sum │ │ ├── parse.go │ │ ├── reflect.go │ │ ├── subcommand.go │ │ └── usage.go │ └── go-scalar │ │ ├── .gitignore │ │ ├── .travis.yml │ │ ├── LICENSE │ │ ├── README.md │ │ ├── go.mod │ │ ├── go.sum │ │ └── scalar.go ├── go-kit │ └── kit │ │ ├── LICENSE │ │ └── log │ │ ├── README.md │ │ ├── doc.go │ │ ├── json_logger.go │ │ ├── level │ │ ├── doc.go │ │ └── level.go │ │ ├── log.go │ │ ├── logfmt_logger.go │ │ ├── nop_logger.go │ │ ├── stdlib.go │ │ ├── sync.go │ │ └── value.go ├── go-logfmt │ └── logfmt │ │ ├── .gitignore │ │ ├── .travis.yml │ │ ├── CHANGELOG.md │ │ ├── LICENSE │ │ ├── README.md │ │ ├── decode.go │ │ ├── doc.go │ │ ├── encode.go │ │ ├── go.mod │ │ └── jsonstring.go ├── golang │ └── protobuf │ │ ├── AUTHORS │ │ ├── CONTRIBUTORS │ │ ├── LICENSE │ │ └── proto │ │ ├── clone.go │ │ ├── decode.go │ │ ├── deprecated.go │ │ ├── discard.go │ │ ├── encode.go │ │ ├── equal.go │ │ ├── extensions.go │ │ ├── lib.go │ │ ├── message_set.go │ │ ├── pointer_reflect.go │ │ ├── pointer_unsafe.go │ │ ├── properties.go │ │ ├── table_marshal.go │ │ ├── table_merge.go │ │ ├── table_unmarshal.go │ │ ├── text.go │ │ └── text_parser.go ├── joho │ └── godotenv │ │ ├── .gitignore │ │ ├── .travis.yml │ │ ├── LICENCE │ │ ├── README.md │ │ └── godotenv.go └── shurcooL │ ├── githubql │ ├── .travis.yml │ ├── LICENSE │ ├── README.md │ ├── doc.go │ ├── enum.go │ ├── githubv4.go │ ├── input.go │ └── scalar.go │ └── graphql │ ├── .travis.yml │ ├── LICENSE │ ├── README.md │ ├── doc.go │ ├── graphql.go │ ├── ident │ └── ident.go │ ├── internal │ └── jsonutil │ │ └── graphql.go │ ├── query.go │ └── scalar.go ├── golang.org └── x │ ├── net │ ├── AUTHORS │ ├── CONTRIBUTORS │ ├── LICENSE │ ├── PATENTS │ └── context │ │ ├── context.go │ │ ├── ctxhttp │ │ └── ctxhttp.go │ │ ├── go17.go │ │ ├── go19.go │ │ ├── pre_go17.go │ │ └── pre_go19.go │ └── oauth2 │ ├── .travis.yml │ ├── AUTHORS │ ├── CONTRIBUTING.md │ ├── CONTRIBUTORS │ ├── LICENSE │ ├── README.md │ ├── go.mod │ ├── go.sum │ ├── internal │ ├── client_appengine.go │ ├── doc.go │ ├── oauth2.go │ ├── token.go │ └── transport.go │ ├── oauth2.go │ ├── token.go │ └── transport.go ├── google.golang.org └── appengine │ ├── LICENSE │ ├── internal │ ├── api.go │ ├── api_classic.go │ ├── api_common.go │ ├── app_id.go │ ├── base │ │ ├── api_base.pb.go │ │ └── api_base.proto │ ├── datastore │ │ ├── datastore_v3.pb.go │ │ └── datastore_v3.proto │ ├── identity.go │ ├── identity_classic.go │ ├── identity_flex.go │ ├── identity_vm.go │ ├── internal.go │ ├── log │ │ ├── log_service.pb.go │ │ └── log_service.proto │ ├── main.go │ ├── main_common.go │ ├── main_vm.go │ ├── metadata.go │ ├── net.go │ ├── regen.sh │ ├── remote_api │ │ ├── remote_api.pb.go │ │ └── remote_api.proto │ ├── transaction.go │ └── urlfetch │ │ ├── urlfetch_service.pb.go │ │ └── urlfetch_service.proto │ └── urlfetch │ └── urlfetch.go └── modules.txt /.env.example: -------------------------------------------------------------------------------- 1 | # This file is only used for local development 2 | GITHUB_TOKEN=XXX 3 | INTERVAL=1m 4 | LOG_LEVEL=debug 5 | SLACK_HOOK=https://hooks.slack.com/services/T02MTEVH7/BASDAHYRZ/XXX... 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.env 2 | /github-releases-notifier 3 | 4 | # Binaries for programs and plugins 5 | *.exe 6 | *.dll 7 | *.so 8 | *.dylib 9 | 10 | # Test binary, build with `go test -c` 11 | *.test 12 | 13 | # Output of the go coverage tool, specifically when used with LiteIDE 14 | *.out 15 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | 3 | go: 4 | - 1.8.x 5 | 6 | before_install: 7 | - go get -v github.com/golang/lint/golint 8 | 9 | script: 10 | - make clean 11 | - make vet 12 | - make lint 13 | - make test 14 | - make build 15 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:1.14 as builder 2 | 3 | ADD . /go/src/github.com/justwatchcom/github-releases-notifier 4 | WORKDIR /go/src/github.com/justwatchcom/github-releases-notifier 5 | 6 | RUN make build 7 | 8 | FROM alpine:3.11 9 | RUN apk --no-cache add ca-certificates 10 | 11 | COPY --from=builder /go/src/github.com/justwatchcom/github-releases-notifier /bin/ 12 | ENTRYPOINT [ "/bin/github-releases-notifier" ] 13 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 JustWatch 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | DIST := dist 2 | BIN := bin 3 | 4 | EXECUTABLE := github-releases-notifier 5 | 6 | PWD := $(shell pwd) 7 | VERSION := $(shell cat VERSION) 8 | SHA := $(shell cat COMMIT 2>/dev/null || git rev-parse --short=8 HEAD) 9 | DATE := $(shell date -u '+%FT%T%z') 10 | 11 | GOLDFLAGS += -X "main.version=$(VERSION)" 12 | GOLDFLAGS += -X "main.date=$(DATE)" 13 | GOLDFLAGS += -X "main.commit=$(SHA)" 14 | GOLDFLAGS += -extldflags '-static' 15 | 16 | GO := CGO_ENABLED=0 go 17 | 18 | GOOS ?= $(shell go version | cut -d' ' -f4 | cut -d'/' -f1) 19 | GOARCH ?= $(shell go version | cut -d' ' -f4 | cut -d'/' -f2) 20 | 21 | PACKAGES ?= $(shell go list ./... | grep -v /vendor/ | grep -v /tests) 22 | 23 | TAGS ?= netgo 24 | 25 | .PHONY: all 26 | all: clean test build 27 | 28 | .PHONY: clean 29 | clean: 30 | $(GO) clean -i ./... 31 | find . -type f -name "coverage.out" -delete 32 | 33 | .PHONY: fmt 34 | fmt: 35 | $(GO) fmt $(PACKAGES) 36 | 37 | .PHONY: tests 38 | tests: test vet lint errcheck megacheck 39 | 40 | .PHONY: vet 41 | vet: 42 | $(GO) vet $(PACKAGES) 43 | 44 | .PHONY: lint 45 | lint: 46 | @which golint > /dev/null; if [ $$? -ne 0 ]; then \ 47 | $(GO) get -u github.com/golang/lint/golint; \ 48 | fi 49 | STATUS=0; for PKG in $(PACKAGES); do golint -set_exit_status $$PKG || STATUS=1; done; exit $$STATUS 50 | 51 | .PHONY: errcheck 52 | errcheck: 53 | @which errcheck > /dev/null; if [ $$? -ne 0 ]; then \ 54 | $(GO) get -u github.com/kisielk/errcheck; \ 55 | fi 56 | STATUS=0; for PKG in $(PACKAGES); do errcheck $$PKG || STATUS=1; done; exit $$STATUS 57 | 58 | .PHONY: megacheck 59 | megacheck: 60 | @which megacheck > /dev/null; if [ $$? -ne 0 ]; then \ 61 | $(GO) get -u honnef.co/go/tools/cmd/megacheck; \ 62 | fi 63 | STATUS=0; for PKG in $(PACKAGES); do megacheck $$PKG || STATUS=1; done; exit $$STATUS 64 | 65 | .PHONY: test 66 | test: 67 | STATUS=0; for PKG in $(PACKAGES); do go test -cover -coverprofile $$GOPATH/src/$$PKG/coverage.out $$PKG || STATUS=1; done; exit $$STATUS 68 | 69 | .PHONY: build 70 | build: $(EXECUTABLE)-$(GOOS)-$(GOARCH) 71 | 72 | $(EXECUTABLE)-$(GOOS)-$(GOARCH): $(wildcard *.go) 73 | $(GO) build -tags '$(TAGS)' -ldflags '-s -w $(GOLDFLAGS)' -o $(EXECUTABLE) 74 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # github-releases-notifier 2 | 3 | [![Build Status](https://travis-ci.org/justwatchcom/github-releases-notifier.svg?branch=master)](https://travis-ci.org/justwatchcom/github-releases-notifier) 4 | [![Go Report Card](https://goreportcard.com/badge/github.com/justwatchcom/github-releases-notifier)](https://goreportcard.com/report/github.com/justwatchcom/github-releases-notifier) 5 | [![Docker Pulls](https://img.shields.io/docker/pulls/justwatch/github-releases-notifier.svg?maxAge=604800)](https://hub.docker.com/r/justwatch/github-releases-notifier) 6 | 7 | Receive Slack notifications if a new release of your favorite software is available on GitHub. 8 | 9 | ![screenshot.png](screenshot.png) 10 | 11 | ### Watching repositories 12 | 13 | To watch repositories simply add them to the list of arguments `-r=kubernetes/kubernetes -r=prometheus/prometheus` and so on. 14 | 15 | ### Deploying 16 | 17 | 1. Get a URL to send WebHooks to your Slack from https://api.slack.com/incoming-webhooks. 18 | 2. Get a token for scraping GitHub: [https://help.github.com/](https://help.github.com/articles/creating-a-personal-access-token-for-the-command-line). 19 | 20 | #### Docker 21 | 22 | ``` 23 | docker run --rm -e GITHUB_TOKEN=XXX -e SLACK_HOOK=https://hooks.slack.com/... justwatch/github-releases-notifier -r=kubernetes/kubernetes 24 | ``` 25 | 26 | #### docker-compose 27 | 28 | 1. Change into the `deployments/` folder. 29 | 2. Open `docker-compose.yml` 30 | 3. Change the token in the environment section to the ones obtained above. 31 | 4. `docker-compose up` 32 | 33 | #### Kubernetes 34 | 35 | ```bash 36 | kubectl create secret generic github-releases-notifier \ 37 | --from-literal=github=XXX` \ 38 | --from-literal=slack=XXX 39 | ``` 40 | 41 | After creating the secret with your credentials you can apply the deployment: 42 | 43 | `kubectl apply -f deployments/kubernetes.yml` 44 | 45 | That's it. -------------------------------------------------------------------------------- /VERSION: -------------------------------------------------------------------------------- 1 | 0.0.1 -------------------------------------------------------------------------------- /deployments/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '2' 2 | 3 | services: 4 | github-releases-notifier: 5 | restart: always 6 | image: justwatch/github-releases-notifier 7 | environment: 8 | - GITHUB_TOKEN=XXX 9 | - SLACK_HOOK=https://hooks.slack.com/services/T02MASDF7/B6WERHYRZ/XXX 10 | command: 11 | - '-r=golang/go' 12 | - '-r=justwatchcom/elasticsearch_exporter' 13 | - '-r=justwatchcom/gopass' 14 | - '-r=justwatchcom/sql_exporter' 15 | - '-r=kubernetes/minikube' 16 | - '-r=prometheus/prometheus' 17 | - '-r=shurcooL/githubql' 18 | -------------------------------------------------------------------------------- /deployments/kubernetes.yml: -------------------------------------------------------------------------------- 1 | apiVersion: extensions/v1beta1 2 | kind: Deployment 3 | metadata: 4 | name: github-releases-notifier 5 | spec: 6 | replicas: 1 7 | revisionHistoryLimit: 10 8 | strategy: 9 | rollingUpdate: 10 | maxSurge: 0 11 | maxUnavailable: 1 12 | type: RollingUpdate 13 | template: 14 | metadata: 15 | labels: 16 | app: github-releases-notifier 17 | spec: 18 | securityContext: 19 | runAsNonRoot: true 20 | runAsUser: 1000 21 | containers: 22 | - name: github-releases-notifier 23 | image: justwatch/github-releases-notifier 24 | env: 25 | - name: GITHUB_TOKEN 26 | valueFrom: 27 | secretKeyRef: 28 | name: github-releases-notifier 29 | key: github 30 | - name: SLACK_HOOK 31 | valueFrom: 32 | secretKeyRef: 33 | name: github-releases-notifier 34 | key: slack 35 | command: 36 | - '/bin/github-releases-notifier' 37 | args: 38 | - '-r=golang/go' 39 | - '-r=justwatchcom/elasticsearch_exporter' 40 | - '-r=justwatchcom/gopass' 41 | - '-r=justwatchcom/sql_exporter' 42 | - '-r=kubernetes/minikube' 43 | - '-r=prometheus/prometheus' 44 | - '-r=shurcooL/githubql' 45 | resources: 46 | limits: 47 | cpu: 100m 48 | memory: 128Mi 49 | requests: 50 | cpu: 25m 51 | memory: 64Mi 52 | restartPolicy: Always 53 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/marthjod/github-releases-notifier 2 | 3 | go 1.14 4 | 5 | require ( 6 | github.com/alexflint/go-arg v1.3.0 7 | github.com/go-kit/kit v0.10.0 8 | github.com/joho/godotenv v1.3.0 9 | github.com/shurcooL/githubql v0.0.0-20191127044304-8f68eb5628d0 10 | github.com/shurcooL/graphql v0.0.0-20181231061246-d48a9a75455f // indirect 11 | golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d 12 | ) 13 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "os" 6 | "strings" 7 | "time" 8 | 9 | "github.com/alexflint/go-arg" 10 | "github.com/go-kit/kit/log" 11 | "github.com/go-kit/kit/log/level" 12 | "github.com/joho/godotenv" 13 | githubql "github.com/shurcooL/githubql" 14 | "golang.org/x/oauth2" 15 | ) 16 | 17 | // Config of env and args 18 | type Config struct { 19 | GithubToken string `arg:"env:GITHUB_TOKEN"` 20 | Interval time.Duration `arg:"env:INTERVAL"` 21 | LogLevel string `arg:"env:LOG_LEVEL"` 22 | Repositories []string `arg:"-r,separate"` 23 | SlackHook string `arg:"env:SLACK_HOOK"` 24 | IgnoreNonstable bool `arg:"env:IGNORE_NONSTABLE"` 25 | } 26 | 27 | // Token returns an oauth2 token or an error. 28 | func (c Config) Token() *oauth2.Token { 29 | return &oauth2.Token{AccessToken: c.GithubToken} 30 | } 31 | 32 | func main() { 33 | _ = godotenv.Load() 34 | 35 | c := Config{ 36 | Interval: time.Hour, 37 | LogLevel: "info", 38 | } 39 | arg.MustParse(&c) 40 | 41 | logger := log.NewJSONLogger(log.NewSyncWriter(os.Stdout)) 42 | logger = log.With(logger, 43 | "ts", log.DefaultTimestampUTC, 44 | "caller", log.Caller(5), 45 | ) 46 | 47 | // level.SetKey("severity") 48 | switch strings.ToLower(c.LogLevel) { 49 | case "debug": 50 | logger = level.NewFilter(logger, level.AllowDebug()) 51 | case "warn": 52 | logger = level.NewFilter(logger, level.AllowWarn()) 53 | case "error": 54 | logger = level.NewFilter(logger, level.AllowError()) 55 | default: 56 | logger = level.NewFilter(logger, level.AllowInfo()) 57 | } 58 | 59 | if len(c.Repositories) == 0 { 60 | level.Error(logger).Log("msg", "no repositories wo watch") 61 | os.Exit(1) 62 | } 63 | 64 | tokenSource := oauth2.StaticTokenSource(c.Token()) 65 | client := oauth2.NewClient(context.Background(), tokenSource) 66 | checker := &Checker{ 67 | logger: logger, 68 | client: githubql.NewClient(client), 69 | } 70 | 71 | // TODO: releases := make(chan Repository, len(c.Repositories)) 72 | releases := make(chan Repository) 73 | go checker.Run(c.Interval, c.Repositories, releases) 74 | 75 | slack := SlackSender{Hook: c.SlackHook} 76 | 77 | level.Info(logger).Log("msg", "waiting for new releases") 78 | for repository := range releases { 79 | if c.IgnoreNonstable && repository.Release.IsNonstable() { 80 | level.Debug(logger).Log("msg", "not notifying about non-stable version", "version", repository.Release.Name) 81 | continue 82 | } 83 | if err := slack.Send(repository); err != nil { 84 | level.Warn(logger).Log( 85 | "msg", "failed to send release to messenger", 86 | "err", err, 87 | ) 88 | continue 89 | } 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /release.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "net/url" 5 | "strings" 6 | "time" 7 | ) 8 | 9 | // Release of a repository tagged via GitHub. 10 | type Release struct { 11 | ID string 12 | Name string 13 | Description string 14 | URL url.URL 15 | PublishedAt time.Time 16 | } 17 | 18 | // IsReleaseCandidate returns true if the release name hints at an RC release. 19 | func (r Release) IsReleaseCandidate() bool { 20 | return strings.Contains(strings.ToLower(r.Name), "-rc") 21 | } 22 | 23 | // IsBeta returns true if the release name hints at a beta version release. 24 | func (r Release) IsBeta() bool { 25 | return strings.Contains(strings.ToLower(r.Name), "beta") 26 | } 27 | 28 | // IsNonstable returns true if one of the non-stable release-checking functions return true. 29 | func (r Release) IsNonstable() bool { 30 | return r.IsReleaseCandidate() || r.IsBeta() 31 | } 32 | -------------------------------------------------------------------------------- /releasechecker.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "strings" 7 | "time" 8 | 9 | "github.com/go-kit/kit/log" 10 | "github.com/go-kit/kit/log/level" 11 | githubql "github.com/shurcooL/githubql" 12 | ) 13 | 14 | // Checker has a githubql client to run queries and also knows about 15 | // the current repositories releases to compare against. 16 | type Checker struct { 17 | logger log.Logger 18 | client *githubql.Client 19 | releases map[string]Repository 20 | } 21 | 22 | // Run the queries and comparisons for the given repositories in a given interval. 23 | func (c *Checker) Run(interval time.Duration, repositories []string, releases chan<- Repository) { 24 | if c.releases == nil { 25 | c.releases = make(map[string]Repository) 26 | } 27 | 28 | for { 29 | for _, repoName := range repositories { 30 | s := strings.Split(repoName, "/") 31 | owner, name := s[0], s[1] 32 | 33 | nextRepo, err := c.query(owner, name) 34 | if err != nil { 35 | level.Warn(c.logger).Log( 36 | "msg", "failed to query the repository's releases", 37 | "owner", owner, 38 | "name", name, 39 | "err", err, 40 | ) 41 | continue 42 | } 43 | 44 | // For debugging uncomment this next line 45 | //releases <- nextRepo 46 | 47 | currRepo, ok := c.releases[repoName] 48 | 49 | // We've queried the repository for the first time. 50 | // Saving the current state to compare with the next iteration. 51 | if !ok { 52 | c.releases[repoName] = nextRepo 53 | continue 54 | } 55 | 56 | if nextRepo.Release.PublishedAt.After(currRepo.Release.PublishedAt) { 57 | releases <- nextRepo 58 | c.releases[repoName] = nextRepo 59 | } else { 60 | level.Debug(c.logger).Log( 61 | "msg", "no new release for repository", 62 | "owner", owner, 63 | "name", name, 64 | ) 65 | } 66 | } 67 | time.Sleep(interval) 68 | } 69 | } 70 | 71 | // This should be improved in the future to make batch requests for all watched repositories at once 72 | // TODO: https://github.com/shurcooL/githubql/issues/17 73 | 74 | func (c *Checker) query(owner, name string) (Repository, error) { 75 | var query struct { 76 | Repository struct { 77 | ID githubql.ID 78 | Name githubql.String 79 | Description githubql.String 80 | URL githubql.URI 81 | 82 | Releases struct { 83 | Edges []struct { 84 | Node struct { 85 | ID githubql.ID 86 | Name githubql.String 87 | Description githubql.String 88 | URL githubql.URI 89 | PublishedAt githubql.DateTime 90 | } 91 | } 92 | } `graphql:"releases(last: 1)"` 93 | } `graphql:"repository(owner: $owner, name: $name)"` 94 | } 95 | 96 | variables := map[string]interface{}{ 97 | "owner": githubql.String(owner), 98 | "name": githubql.String(name), 99 | } 100 | 101 | ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) 102 | defer cancel() 103 | if err := c.client.Query(ctx, &query, variables); err != nil { 104 | return Repository{}, err 105 | } 106 | 107 | repositoryID, ok := query.Repository.ID.(string) 108 | if !ok { 109 | return Repository{}, fmt.Errorf("can't convert repository id to string: %v", query.Repository.ID) 110 | } 111 | 112 | if len(query.Repository.Releases.Edges) == 0 { 113 | return Repository{}, fmt.Errorf("can't find any releases for %s/%s", owner, name) 114 | } 115 | latestRelease := query.Repository.Releases.Edges[0].Node 116 | 117 | releaseID, ok := latestRelease.ID.(string) 118 | if !ok { 119 | return Repository{}, fmt.Errorf("can't convert release id to string: %v", query.Repository.ID) 120 | } 121 | 122 | return Repository{ 123 | ID: repositoryID, 124 | Name: string(query.Repository.Name), 125 | Owner: owner, 126 | Description: string(query.Repository.Description), 127 | URL: *query.Repository.URL.URL, 128 | 129 | Release: Release{ 130 | ID: releaseID, 131 | Name: string(latestRelease.Name), 132 | Description: string(latestRelease.Description), 133 | URL: *latestRelease.URL.URL, 134 | PublishedAt: latestRelease.PublishedAt.Time, 135 | }, 136 | }, nil 137 | } 138 | -------------------------------------------------------------------------------- /repository.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "net/url" 4 | 5 | // Repository on GitHub. 6 | type Repository struct { 7 | ID string 8 | Name string 9 | Owner string 10 | Description string 11 | URL url.URL 12 | Release Release 13 | } 14 | -------------------------------------------------------------------------------- /screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/justwatchcom/github-releases-notifier/ecaa016a61c3d5f338c7de552606c567a95fc96d/screenshot.png -------------------------------------------------------------------------------- /slack.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "context" 6 | "encoding/json" 7 | "fmt" 8 | "io/ioutil" 9 | "net/http" 10 | "time" 11 | ) 12 | 13 | // SlackSender has the hook to send slack notifications. 14 | type SlackSender struct { 15 | Hook string 16 | } 17 | 18 | type slackPayload struct { 19 | Username string `json:"username"` 20 | IconEmoji string `json:"icon_emoji"` 21 | Text string `json:"text"` 22 | } 23 | 24 | // Send a notification with a formatted message build from the repository. 25 | func (s *SlackSender) Send(repository Repository) error { 26 | payload := slackPayload{ 27 | Username: "GitHub Releases", 28 | IconEmoji: ":github:", 29 | Text: fmt.Sprintf( 30 | "<%s|%s/%s>: <%s|%s> released", 31 | repository.URL.String(), 32 | repository.Owner, 33 | repository.Name, 34 | repository.Release.URL.String(), 35 | repository.Release.Name, 36 | ), 37 | } 38 | 39 | payloadData, err := json.Marshal(payload) 40 | if err != nil { 41 | return err 42 | } 43 | 44 | req, err := http.NewRequest(http.MethodPost, s.Hook, bytes.NewReader(payloadData)) 45 | if err != nil { 46 | return err 47 | } 48 | ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) 49 | req = req.WithContext(ctx) 50 | defer cancel() 51 | 52 | resp, err := http.DefaultClient.Do(req) 53 | if err != nil { 54 | return err 55 | } 56 | defer resp.Body.Close() 57 | 58 | if resp.StatusCode != http.StatusOK { 59 | body, _ := ioutil.ReadAll(resp.Body) 60 | return fmt.Errorf("request didn't respond with 200 OK: %s, %s", resp.Status, body) 61 | } 62 | 63 | return nil 64 | } 65 | -------------------------------------------------------------------------------- /vendor/github.com/alexflint/go-arg/.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files, Static and Dynamic libs (Shared Objects) 2 | *.o 3 | *.a 4 | *.so 5 | 6 | # Folders 7 | _obj 8 | _test 9 | 10 | # Architecture specific extensions/prefixes 11 | *.[568vq] 12 | [568vq].out 13 | 14 | *.cgo1.go 15 | *.cgo2.c 16 | _cgo_defun.c 17 | _cgo_gotypes.go 18 | _cgo_export.* 19 | 20 | _testmain.go 21 | 22 | *.exe 23 | *.test 24 | *.prof 25 | -------------------------------------------------------------------------------- /vendor/github.com/alexflint/go-arg/.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | go: 3 | - "1.12" 4 | - "1.13" 5 | before_install: 6 | - go get github.com/axw/gocov/gocov 7 | - go get github.com/mattn/goveralls 8 | - if ! go get github.com/golang/tools/cmd/cover; then go get golang.org/x/tools/cmd/cover; fi 9 | script: 10 | - $HOME/gopath/bin/goveralls -service=travis-ci 11 | - bash test/compile_with_go110.sh 12 | - bash test/compile_with_go111.sh 13 | - bash test/compile_with_go111_inside_gopath.sh 14 | -------------------------------------------------------------------------------- /vendor/github.com/alexflint/go-arg/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015, Alex Flint 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 10 | * Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | 14 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 15 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 17 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 18 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 19 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 20 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 21 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 22 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 23 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | 25 | -------------------------------------------------------------------------------- /vendor/github.com/alexflint/go-arg/doc.go: -------------------------------------------------------------------------------- 1 | // Package arg parses command line arguments using the fields from a struct. 2 | // 3 | // For example, 4 | // 5 | // var args struct { 6 | // Iter int 7 | // Debug bool 8 | // } 9 | // arg.MustParse(&args) 10 | // 11 | // defines two command line arguments, which can be set using any of 12 | // 13 | // ./example --iter=1 --debug // debug is a boolean flag so its value is set to true 14 | // ./example -iter 1 // debug defaults to its zero value (false) 15 | // ./example --debug=true // iter defaults to its zero value (zero) 16 | // 17 | // The fastest way to see how to use go-arg is to read the examples below. 18 | // 19 | // Fields can be bool, string, any float type, or any signed or unsigned integer type. 20 | // They can also be slices of any of the above, or slices of pointers to any of the above. 21 | // 22 | // Tags can be specified using the `arg` and `help` tag names: 23 | // 24 | // var args struct { 25 | // Input string `arg:"positional"` 26 | // Log string `arg:"positional,required"` 27 | // Debug bool `arg:"-d" help:"turn on debug mode"` 28 | // RealMode bool `arg:"--real" 29 | // Wr io.Writer `arg:"-"` 30 | // } 31 | // 32 | // Any tag string that starts with a single hyphen is the short form for an argument 33 | // (e.g. `./example -d`), and any tag string that starts with two hyphens is the long 34 | // form for the argument (instead of the field name). 35 | // 36 | // Other valid tag strings are `positional` and `required`. 37 | // 38 | // Fields can be excluded from processing with `arg:"-"`. 39 | package arg 40 | -------------------------------------------------------------------------------- /vendor/github.com/alexflint/go-arg/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/alexflint/go-arg 2 | 3 | require ( 4 | github.com/alexflint/go-scalar v1.0.0 5 | github.com/stretchr/testify v1.2.2 6 | ) 7 | 8 | go 1.13 9 | -------------------------------------------------------------------------------- /vendor/github.com/alexflint/go-arg/go.sum: -------------------------------------------------------------------------------- 1 | github.com/alexflint/go-scalar v1.0.0 h1:NGupf1XV/Xb04wXskDFzS0KWOLH632W/EO4fAFi+A70= 2 | github.com/alexflint/go-scalar v1.0.0/go.mod h1:GpHzbCOZXEKMEcygYQ5n/aa4Aq84zbxjy3MxYW0gjYw= 3 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 4 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 5 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 6 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 7 | github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w= 8 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 9 | -------------------------------------------------------------------------------- /vendor/github.com/alexflint/go-arg/reflect.go: -------------------------------------------------------------------------------- 1 | package arg 2 | 3 | import ( 4 | "encoding" 5 | "reflect" 6 | 7 | scalar "github.com/alexflint/go-scalar" 8 | ) 9 | 10 | var textUnmarshalerType = reflect.TypeOf([]encoding.TextUnmarshaler{}).Elem() 11 | 12 | // canParse returns true if the type can be parsed from a string 13 | func canParse(t reflect.Type) (parseable, boolean, multiple bool) { 14 | parseable = scalar.CanParse(t) 15 | boolean = isBoolean(t) 16 | if parseable { 17 | return 18 | } 19 | 20 | // Look inside pointer types 21 | if t.Kind() == reflect.Ptr { 22 | t = t.Elem() 23 | } 24 | // Look inside slice types 25 | if t.Kind() == reflect.Slice { 26 | multiple = true 27 | t = t.Elem() 28 | } 29 | 30 | parseable = scalar.CanParse(t) 31 | boolean = isBoolean(t) 32 | if parseable { 33 | return 34 | } 35 | 36 | // Look inside pointer types (again, in case of []*Type) 37 | if t.Kind() == reflect.Ptr { 38 | t = t.Elem() 39 | } 40 | 41 | parseable = scalar.CanParse(t) 42 | boolean = isBoolean(t) 43 | if parseable { 44 | return 45 | } 46 | 47 | return false, false, false 48 | } 49 | 50 | // isBoolean returns true if the type can be parsed from a single string 51 | func isBoolean(t reflect.Type) bool { 52 | switch { 53 | case t.Implements(textUnmarshalerType): 54 | return false 55 | case t.Kind() == reflect.Bool: 56 | return true 57 | case t.Kind() == reflect.Ptr && t.Elem().Kind() == reflect.Bool: 58 | return true 59 | default: 60 | return false 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /vendor/github.com/alexflint/go-arg/subcommand.go: -------------------------------------------------------------------------------- 1 | package arg 2 | 3 | // Subcommand returns the user struct for the subcommand selected by 4 | // the command line arguments most recently processed by the parser. 5 | // The return value is always a pointer to a struct. If no subcommand 6 | // was specified then it returns the top-level arguments struct. If 7 | // no command line arguments have been processed by this parser then it 8 | // returns nil. 9 | func (p *Parser) Subcommand() interface{} { 10 | if p.lastCmd == nil || p.lastCmd.parent == nil { 11 | return nil 12 | } 13 | return p.val(p.lastCmd.dest).Interface() 14 | } 15 | 16 | // SubcommandNames returns the sequence of subcommands specified by the 17 | // user. If no subcommands were given then it returns an empty slice. 18 | func (p *Parser) SubcommandNames() []string { 19 | if p.lastCmd == nil { 20 | return nil 21 | } 22 | 23 | // make a list of ancestor commands 24 | var ancestors []string 25 | cur := p.lastCmd 26 | for cur.parent != nil { // we want to exclude the root 27 | ancestors = append(ancestors, cur.name) 28 | cur = cur.parent 29 | } 30 | 31 | // reverse the list 32 | out := make([]string, len(ancestors)) 33 | for i := 0; i < len(ancestors); i++ { 34 | out[i] = ancestors[len(ancestors)-i-1] 35 | } 36 | return out 37 | } 38 | -------------------------------------------------------------------------------- /vendor/github.com/alexflint/go-arg/usage.go: -------------------------------------------------------------------------------- 1 | package arg 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "os" 7 | "strings" 8 | ) 9 | 10 | // the width of the left column 11 | const colWidth = 25 12 | 13 | // to allow monkey patching in tests 14 | var stderr = os.Stderr 15 | 16 | // Fail prints usage information to stderr and exits with non-zero status 17 | func (p *Parser) Fail(msg string) { 18 | p.failWithCommand(msg, p.cmd) 19 | } 20 | 21 | // failWithCommand prints usage information for the given subcommand to stderr and exits with non-zero status 22 | func (p *Parser) failWithCommand(msg string, cmd *command) { 23 | p.writeUsageForCommand(stderr, cmd) 24 | fmt.Fprintln(stderr, "error:", msg) 25 | osExit(-1) 26 | } 27 | 28 | // WriteUsage writes usage information to the given writer 29 | func (p *Parser) WriteUsage(w io.Writer) { 30 | p.writeUsageForCommand(w, p.cmd) 31 | } 32 | 33 | // writeUsageForCommand writes usage information for the given subcommand 34 | func (p *Parser) writeUsageForCommand(w io.Writer, cmd *command) { 35 | var positionals, options []*spec 36 | for _, spec := range cmd.specs { 37 | if spec.positional { 38 | positionals = append(positionals, spec) 39 | } else { 40 | options = append(options, spec) 41 | } 42 | } 43 | 44 | if p.version != "" { 45 | fmt.Fprintln(w, p.version) 46 | } 47 | 48 | // make a list of ancestor commands so that we print with full context 49 | var ancestors []string 50 | ancestor := cmd 51 | for ancestor != nil { 52 | ancestors = append(ancestors, ancestor.name) 53 | ancestor = ancestor.parent 54 | } 55 | 56 | // print the beginning of the usage string 57 | fmt.Fprint(w, "Usage:") 58 | for i := len(ancestors) - 1; i >= 0; i-- { 59 | fmt.Fprint(w, " "+ancestors[i]) 60 | } 61 | 62 | // write the option component of the usage message 63 | for _, spec := range options { 64 | // prefix with a space 65 | fmt.Fprint(w, " ") 66 | if !spec.required { 67 | fmt.Fprint(w, "[") 68 | } 69 | fmt.Fprint(w, synopsis(spec, "--"+spec.long)) 70 | if !spec.required { 71 | fmt.Fprint(w, "]") 72 | } 73 | } 74 | 75 | // write the positional component of the usage message 76 | for _, spec := range positionals { 77 | // prefix with a space 78 | fmt.Fprint(w, " ") 79 | if spec.multiple { 80 | if !spec.required { 81 | fmt.Fprint(w, "[") 82 | } 83 | fmt.Fprintf(w, "%s [%s ...]", spec.placeholder, spec.placeholder) 84 | if !spec.required { 85 | fmt.Fprint(w, "]") 86 | } 87 | } else { 88 | fmt.Fprint(w, spec.placeholder) 89 | } 90 | } 91 | 92 | // if the program supports subcommands, give a hint to the user about their existence 93 | if len(cmd.subcommands) > 0 { 94 | fmt.Fprint(w, " []") 95 | } 96 | 97 | fmt.Fprint(w, "\n") 98 | } 99 | 100 | func printTwoCols(w io.Writer, left, help string, defaultVal string) { 101 | lhs := " " + left 102 | fmt.Fprint(w, lhs) 103 | if help != "" { 104 | if len(lhs)+2 < colWidth { 105 | fmt.Fprint(w, strings.Repeat(" ", colWidth-len(lhs))) 106 | } else { 107 | fmt.Fprint(w, "\n"+strings.Repeat(" ", colWidth)) 108 | } 109 | fmt.Fprint(w, help) 110 | } 111 | if defaultVal != "" { 112 | fmt.Fprintf(w, " [default: %s]", defaultVal) 113 | } 114 | fmt.Fprint(w, "\n") 115 | } 116 | 117 | // WriteHelp writes the usage string followed by the full help string for each option 118 | func (p *Parser) WriteHelp(w io.Writer) { 119 | p.writeHelpForCommand(w, p.cmd) 120 | } 121 | 122 | // writeHelp writes the usage string for the given subcommand 123 | func (p *Parser) writeHelpForCommand(w io.Writer, cmd *command) { 124 | var positionals, options []*spec 125 | for _, spec := range cmd.specs { 126 | if spec.positional { 127 | positionals = append(positionals, spec) 128 | } else { 129 | options = append(options, spec) 130 | } 131 | } 132 | 133 | if p.description != "" { 134 | fmt.Fprintln(w, p.description) 135 | } 136 | p.writeUsageForCommand(w, cmd) 137 | 138 | // write the list of positionals 139 | if len(positionals) > 0 { 140 | fmt.Fprint(w, "\nPositional arguments:\n") 141 | for _, spec := range positionals { 142 | printTwoCols(w, spec.placeholder, spec.help, "") 143 | } 144 | } 145 | 146 | // write the list of options 147 | fmt.Fprint(w, "\nOptions:\n") 148 | for _, spec := range options { 149 | p.printOption(w, spec) 150 | } 151 | 152 | // write the list of built in options 153 | p.printOption(w, &spec{ 154 | boolean: true, 155 | long: "help", 156 | short: "h", 157 | help: "display this help and exit", 158 | }) 159 | if p.version != "" { 160 | p.printOption(w, &spec{ 161 | boolean: true, 162 | long: "version", 163 | help: "display version and exit", 164 | }) 165 | } 166 | 167 | // write the list of subcommands 168 | if len(cmd.subcommands) > 0 { 169 | fmt.Fprint(w, "\nCommands:\n") 170 | for _, subcmd := range cmd.subcommands { 171 | printTwoCols(w, subcmd.name, subcmd.help, "") 172 | } 173 | } 174 | } 175 | 176 | func (p *Parser) printOption(w io.Writer, spec *spec) { 177 | left := synopsis(spec, "--"+spec.long) 178 | if spec.short != "" { 179 | left += ", " + synopsis(spec, "-"+spec.short) 180 | } 181 | printTwoCols(w, left, spec.help, spec.defaultVal) 182 | } 183 | 184 | func synopsis(spec *spec, form string) string { 185 | if spec.boolean { 186 | return form 187 | } 188 | return form + " " + spec.placeholder 189 | } 190 | 191 | func ptrTo(s string) *string { 192 | return &s 193 | } 194 | -------------------------------------------------------------------------------- /vendor/github.com/alexflint/go-scalar/.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files, Static and Dynamic libs (Shared Objects) 2 | *.o 3 | *.a 4 | *.so 5 | 6 | # Folders 7 | _obj 8 | _test 9 | 10 | # Architecture specific extensions/prefixes 11 | *.[568vq] 12 | [568vq].out 13 | 14 | *.cgo1.go 15 | *.cgo2.c 16 | _cgo_defun.c 17 | _cgo_gotypes.go 18 | _cgo_export.* 19 | 20 | _testmain.go 21 | 22 | *.exe 23 | *.test 24 | *.prof 25 | -------------------------------------------------------------------------------- /vendor/github.com/alexflint/go-scalar/.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | go: 3 | - tip 4 | before_install: 5 | - go get github.com/axw/gocov/gocov 6 | - go get github.com/mattn/goveralls 7 | - if ! go get github.com/golang/tools/cmd/cover; then go get golang.org/x/tools/cmd/cover; fi 8 | script: 9 | - $HOME/gopath/bin/goveralls -service=travis-ci 10 | -------------------------------------------------------------------------------- /vendor/github.com/alexflint/go-scalar/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015, Alex Flint 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 10 | * Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | 14 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 15 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 17 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 18 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 19 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 20 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 21 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 22 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 23 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | 25 | -------------------------------------------------------------------------------- /vendor/github.com/alexflint/go-scalar/README.md: -------------------------------------------------------------------------------- 1 | [![GoDoc](https://godoc.org/github.com/alexflint/go-scalar?status.svg)](https://godoc.org/github.com/alexflint/go-scalar) 2 | [![Build Status](https://travis-ci.org/alexflint/go-scalar.svg?branch=master)](https://travis-ci.org/alexflint/go-scalar) 3 | [![Coverage Status](https://coveralls.io/repos/alexflint/go-scalar/badge.svg?branch=master&service=github)](https://coveralls.io/github/alexflint/go-scalar?branch=master) 4 | [![Report Card](https://goreportcard.com/badge/github.com/alexflint/go-scalar)](https://goreportcard.com/badge/github.com/alexflint/go-scalar) 5 | 6 | ## Scalar parsing library 7 | 8 | Scalar is a library for parsing strings into arbitrary scalars (integers, 9 | floats, strings, booleans, etc). It is helpful for tasks such as parsing 10 | strings passed as environment variables or command line arguments. 11 | 12 | ```shell 13 | go get github.com/alexflint/go-scalar 14 | ``` 15 | 16 | The main API works as follows: 17 | 18 | ```go 19 | var value int 20 | err := scalar.Parse(&value, "123") 21 | ``` 22 | 23 | There is also a variant that takes a `reflect.Value`: 24 | 25 | ```go 26 | var value int 27 | err := scalar.ParseValue(reflect.ValueOf(&value), "123") 28 | ``` 29 | -------------------------------------------------------------------------------- /vendor/github.com/alexflint/go-scalar/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/alexflint/go-scalar 2 | 3 | require ( 4 | github.com/davecgh/go-spew v1.1.1 // indirect 5 | github.com/pmezard/go-difflib v1.0.0 // indirect 6 | github.com/stretchr/testify v1.2.2 7 | ) 8 | -------------------------------------------------------------------------------- /vendor/github.com/alexflint/go-scalar/go.sum: -------------------------------------------------------------------------------- 1 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 2 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 3 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 4 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 5 | github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w= 6 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 7 | -------------------------------------------------------------------------------- /vendor/github.com/alexflint/go-scalar/scalar.go: -------------------------------------------------------------------------------- 1 | // Package scalar parses strings into values of scalar type. 2 | 3 | package scalar 4 | 5 | import ( 6 | "encoding" 7 | "errors" 8 | "fmt" 9 | "net" 10 | "net/mail" 11 | "reflect" 12 | "strconv" 13 | "time" 14 | ) 15 | 16 | // The reflected form of some special types 17 | var ( 18 | textUnmarshalerType = reflect.TypeOf([]encoding.TextUnmarshaler{}).Elem() 19 | durationType = reflect.TypeOf(time.Duration(0)) 20 | mailAddressType = reflect.TypeOf(mail.Address{}) 21 | macType = reflect.TypeOf(net.HardwareAddr{}) 22 | ) 23 | 24 | var ( 25 | errNotSettable = errors.New("value is not settable") 26 | errPtrNotSettable = errors.New("value is a nil pointer and is not settable") 27 | ) 28 | 29 | // Parse assigns a value to v by parsing s. 30 | func Parse(dest interface{}, s string) error { 31 | return ParseValue(reflect.ValueOf(dest), s) 32 | } 33 | 34 | // ParseValue assigns a value to v by parsing s. 35 | func ParseValue(v reflect.Value, s string) error { 36 | // If we have a nil pointer then allocate a new object 37 | if v.Kind() == reflect.Ptr && v.IsNil() { 38 | if !v.CanSet() { 39 | return errPtrNotSettable 40 | } 41 | 42 | v.Set(reflect.New(v.Type().Elem())) 43 | } 44 | 45 | // If it implements encoding.TextUnmarshaler then use that 46 | if scalar, ok := v.Interface().(encoding.TextUnmarshaler); ok { 47 | return scalar.UnmarshalText([]byte(s)) 48 | } 49 | // If it's a value instead of a pointer, check that we can unmarshal it 50 | // via TextUnmarshaler as well 51 | if v.CanAddr() { 52 | if scalar, ok := v.Addr().Interface().(encoding.TextUnmarshaler); ok { 53 | return scalar.UnmarshalText([]byte(s)) 54 | } 55 | } 56 | 57 | // If we have a pointer then dereference it 58 | if v.Kind() == reflect.Ptr { 59 | v = v.Elem() 60 | } 61 | 62 | if !v.CanSet() { 63 | return errNotSettable 64 | } 65 | 66 | // Switch on concrete type 67 | switch scalar := v.Interface(); scalar.(type) { 68 | case time.Duration: 69 | duration, err := time.ParseDuration(s) 70 | if err != nil { 71 | return err 72 | } 73 | v.Set(reflect.ValueOf(duration)) 74 | return nil 75 | case mail.Address: 76 | addr, err := mail.ParseAddress(s) 77 | if err != nil { 78 | return err 79 | } 80 | v.Set(reflect.ValueOf(*addr)) 81 | return nil 82 | case net.HardwareAddr: 83 | ip, err := net.ParseMAC(s) 84 | if err != nil { 85 | return err 86 | } 87 | v.Set(reflect.ValueOf(ip)) 88 | return nil 89 | } 90 | 91 | // Switch on kind so that we can handle derived types 92 | switch v.Kind() { 93 | case reflect.String: 94 | v.SetString(s) 95 | case reflect.Bool: 96 | x, err := strconv.ParseBool(s) 97 | if err != nil { 98 | return err 99 | } 100 | v.SetBool(x) 101 | case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: 102 | x, err := strconv.ParseInt(s, 10, v.Type().Bits()) 103 | if err != nil { 104 | return err 105 | } 106 | v.SetInt(x) 107 | case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: 108 | x, err := strconv.ParseUint(s, 10, v.Type().Bits()) 109 | if err != nil { 110 | return err 111 | } 112 | v.SetUint(x) 113 | case reflect.Float32, reflect.Float64: 114 | x, err := strconv.ParseFloat(s, v.Type().Bits()) 115 | if err != nil { 116 | return err 117 | } 118 | v.SetFloat(x) 119 | default: 120 | return fmt.Errorf("cannot parse into %v", v.Type()) 121 | } 122 | return nil 123 | } 124 | 125 | // CanParse returns true if the type can be parsed from a string. 126 | func CanParse(t reflect.Type) bool { 127 | // If it implements encoding.TextUnmarshaler then use that 128 | if t.Implements(textUnmarshalerType) || reflect.PtrTo(t).Implements(textUnmarshalerType) { 129 | return true 130 | } 131 | 132 | // If we have a pointer then dereference it 133 | if t.Kind() == reflect.Ptr { 134 | t = t.Elem() 135 | } 136 | 137 | // Check for other special types 138 | switch t { 139 | case durationType, mailAddressType, macType: 140 | return true 141 | } 142 | 143 | // Fall back to checking the kind 144 | switch t.Kind() { 145 | case reflect.Bool: 146 | return true 147 | case reflect.String, reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, 148 | reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr, 149 | reflect.Float32, reflect.Float64: 150 | return true 151 | } 152 | return false 153 | } 154 | -------------------------------------------------------------------------------- /vendor/github.com/go-kit/kit/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Peter Bourgon 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /vendor/github.com/go-kit/kit/log/README.md: -------------------------------------------------------------------------------- 1 | # package log 2 | 3 | `package log` provides a minimal interface for structured logging in services. 4 | It may be wrapped to encode conventions, enforce type-safety, provide leveled 5 | logging, and so on. It can be used for both typical application log events, 6 | and log-structured data streams. 7 | 8 | ## Structured logging 9 | 10 | Structured logging is, basically, conceding to the reality that logs are 11 | _data_, and warrant some level of schematic rigor. Using a stricter, 12 | key/value-oriented message format for our logs, containing contextual and 13 | semantic information, makes it much easier to get insight into the 14 | operational activity of the systems we build. Consequently, `package log` is 15 | of the strong belief that "[the benefits of structured logging outweigh the 16 | minimal effort involved](https://www.thoughtworks.com/radar/techniques/structured-logging)". 17 | 18 | Migrating from unstructured to structured logging is probably a lot easier 19 | than you'd expect. 20 | 21 | ```go 22 | // Unstructured 23 | log.Printf("HTTP server listening on %s", addr) 24 | 25 | // Structured 26 | logger.Log("transport", "HTTP", "addr", addr, "msg", "listening") 27 | ``` 28 | 29 | ## Usage 30 | 31 | ### Typical application logging 32 | 33 | ```go 34 | w := log.NewSyncWriter(os.Stderr) 35 | logger := log.NewLogfmtLogger(w) 36 | logger.Log("question", "what is the meaning of life?", "answer", 42) 37 | 38 | // Output: 39 | // question="what is the meaning of life?" answer=42 40 | ``` 41 | 42 | ### Contextual Loggers 43 | 44 | ```go 45 | func main() { 46 | var logger log.Logger 47 | logger = log.NewLogfmtLogger(log.NewSyncWriter(os.Stderr)) 48 | logger = log.With(logger, "instance_id", 123) 49 | 50 | logger.Log("msg", "starting") 51 | NewWorker(log.With(logger, "component", "worker")).Run() 52 | NewSlacker(log.With(logger, "component", "slacker")).Run() 53 | } 54 | 55 | // Output: 56 | // instance_id=123 msg=starting 57 | // instance_id=123 component=worker msg=running 58 | // instance_id=123 component=slacker msg=running 59 | ``` 60 | 61 | ### Interact with stdlib logger 62 | 63 | Redirect stdlib logger to Go kit logger. 64 | 65 | ```go 66 | import ( 67 | "os" 68 | stdlog "log" 69 | kitlog "github.com/go-kit/kit/log" 70 | ) 71 | 72 | func main() { 73 | logger := kitlog.NewJSONLogger(kitlog.NewSyncWriter(os.Stdout)) 74 | stdlog.SetOutput(kitlog.NewStdlibAdapter(logger)) 75 | stdlog.Print("I sure like pie") 76 | } 77 | 78 | // Output: 79 | // {"msg":"I sure like pie","ts":"2016/01/01 12:34:56"} 80 | ``` 81 | 82 | Or, if, for legacy reasons, you need to pipe all of your logging through the 83 | stdlib log package, you can redirect Go kit logger to the stdlib logger. 84 | 85 | ```go 86 | logger := kitlog.NewLogfmtLogger(kitlog.StdlibWriter{}) 87 | logger.Log("legacy", true, "msg", "at least it's something") 88 | 89 | // Output: 90 | // 2016/01/01 12:34:56 legacy=true msg="at least it's something" 91 | ``` 92 | 93 | ### Timestamps and callers 94 | 95 | ```go 96 | var logger log.Logger 97 | logger = log.NewLogfmtLogger(log.NewSyncWriter(os.Stderr)) 98 | logger = log.With(logger, "ts", log.DefaultTimestampUTC, "caller", log.DefaultCaller) 99 | 100 | logger.Log("msg", "hello") 101 | 102 | // Output: 103 | // ts=2016-01-01T12:34:56Z caller=main.go:15 msg=hello 104 | ``` 105 | 106 | ## Levels 107 | 108 | Log levels are supported via the [level package](https://godoc.org/github.com/go-kit/kit/log/level). 109 | 110 | ## Supported output formats 111 | 112 | - [Logfmt](https://brandur.org/logfmt) ([see also](https://blog.codeship.com/logfmt-a-log-format-thats-easy-to-read-and-write)) 113 | - JSON 114 | 115 | ## Enhancements 116 | 117 | `package log` is centered on the one-method Logger interface. 118 | 119 | ```go 120 | type Logger interface { 121 | Log(keyvals ...interface{}) error 122 | } 123 | ``` 124 | 125 | This interface, and its supporting code like is the product of much iteration 126 | and evaluation. For more details on the evolution of the Logger interface, 127 | see [The Hunt for a Logger Interface](http://go-talks.appspot.com/github.com/ChrisHines/talks/structured-logging/structured-logging.slide#1), 128 | a talk by [Chris Hines](https://github.com/ChrisHines). 129 | Also, please see 130 | [#63](https://github.com/go-kit/kit/issues/63), 131 | [#76](https://github.com/go-kit/kit/pull/76), 132 | [#131](https://github.com/go-kit/kit/issues/131), 133 | [#157](https://github.com/go-kit/kit/pull/157), 134 | [#164](https://github.com/go-kit/kit/issues/164), and 135 | [#252](https://github.com/go-kit/kit/pull/252) 136 | to review historical conversations about package log and the Logger interface. 137 | 138 | Value-add packages and suggestions, 139 | like improvements to [the leveled logger](https://godoc.org/github.com/go-kit/kit/log/level), 140 | are of course welcome. Good proposals should 141 | 142 | - Be composable with [contextual loggers](https://godoc.org/github.com/go-kit/kit/log#With), 143 | - Not break the behavior of [log.Caller](https://godoc.org/github.com/go-kit/kit/log#Caller) in any wrapped contextual loggers, and 144 | - Be friendly to packages that accept only an unadorned log.Logger. 145 | 146 | ## Benchmarks & comparisons 147 | 148 | There are a few Go logging benchmarks and comparisons that include Go kit's package log. 149 | 150 | - [imkira/go-loggers-bench](https://github.com/imkira/go-loggers-bench) includes kit/log 151 | - [uber-common/zap](https://github.com/uber-common/zap), a zero-alloc logging library, includes a comparison with kit/log 152 | -------------------------------------------------------------------------------- /vendor/github.com/go-kit/kit/log/doc.go: -------------------------------------------------------------------------------- 1 | // Package log provides a structured logger. 2 | // 3 | // Structured logging produces logs easily consumed later by humans or 4 | // machines. Humans might be interested in debugging errors, or tracing 5 | // specific requests. Machines might be interested in counting interesting 6 | // events, or aggregating information for off-line processing. In both cases, 7 | // it is important that the log messages are structured and actionable. 8 | // Package log is designed to encourage both of these best practices. 9 | // 10 | // Basic Usage 11 | // 12 | // The fundamental interface is Logger. Loggers create log events from 13 | // key/value data. The Logger interface has a single method, Log, which 14 | // accepts a sequence of alternating key/value pairs, which this package names 15 | // keyvals. 16 | // 17 | // type Logger interface { 18 | // Log(keyvals ...interface{}) error 19 | // } 20 | // 21 | // Here is an example of a function using a Logger to create log events. 22 | // 23 | // func RunTask(task Task, logger log.Logger) string { 24 | // logger.Log("taskID", task.ID, "event", "starting task") 25 | // ... 26 | // logger.Log("taskID", task.ID, "event", "task complete") 27 | // } 28 | // 29 | // The keys in the above example are "taskID" and "event". The values are 30 | // task.ID, "starting task", and "task complete". Every key is followed 31 | // immediately by its value. 32 | // 33 | // Keys are usually plain strings. Values may be any type that has a sensible 34 | // encoding in the chosen log format. With structured logging it is a good 35 | // idea to log simple values without formatting them. This practice allows 36 | // the chosen logger to encode values in the most appropriate way. 37 | // 38 | // Contextual Loggers 39 | // 40 | // A contextual logger stores keyvals that it includes in all log events. 41 | // Building appropriate contextual loggers reduces repetition and aids 42 | // consistency in the resulting log output. With and WithPrefix add context to 43 | // a logger. We can use With to improve the RunTask example. 44 | // 45 | // func RunTask(task Task, logger log.Logger) string { 46 | // logger = log.With(logger, "taskID", task.ID) 47 | // logger.Log("event", "starting task") 48 | // ... 49 | // taskHelper(task.Cmd, logger) 50 | // ... 51 | // logger.Log("event", "task complete") 52 | // } 53 | // 54 | // The improved version emits the same log events as the original for the 55 | // first and last calls to Log. Passing the contextual logger to taskHelper 56 | // enables each log event created by taskHelper to include the task.ID even 57 | // though taskHelper does not have access to that value. Using contextual 58 | // loggers this way simplifies producing log output that enables tracing the 59 | // life cycle of individual tasks. (See the Contextual example for the full 60 | // code of the above snippet.) 61 | // 62 | // Dynamic Contextual Values 63 | // 64 | // A Valuer function stored in a contextual logger generates a new value each 65 | // time an event is logged. The Valuer example demonstrates how this feature 66 | // works. 67 | // 68 | // Valuers provide the basis for consistently logging timestamps and source 69 | // code location. The log package defines several valuers for that purpose. 70 | // See Timestamp, DefaultTimestamp, DefaultTimestampUTC, Caller, and 71 | // DefaultCaller. A common logger initialization sequence that ensures all log 72 | // entries contain a timestamp and source location looks like this: 73 | // 74 | // logger := log.NewLogfmtLogger(log.NewSyncWriter(os.Stdout)) 75 | // logger = log.With(logger, "ts", log.DefaultTimestampUTC, "caller", log.DefaultCaller) 76 | // 77 | // Concurrent Safety 78 | // 79 | // Applications with multiple goroutines want each log event written to the 80 | // same logger to remain separate from other log events. Package log provides 81 | // two simple solutions for concurrent safe logging. 82 | // 83 | // NewSyncWriter wraps an io.Writer and serializes each call to its Write 84 | // method. Using a SyncWriter has the benefit that the smallest practical 85 | // portion of the logging logic is performed within a mutex, but it requires 86 | // the formatting Logger to make only one call to Write per log event. 87 | // 88 | // NewSyncLogger wraps any Logger and serializes each call to its Log method. 89 | // Using a SyncLogger has the benefit that it guarantees each log event is 90 | // handled atomically within the wrapped logger, but it typically serializes 91 | // both the formatting and output logic. Use a SyncLogger if the formatting 92 | // logger may perform multiple writes per log event. 93 | // 94 | // Error Handling 95 | // 96 | // This package relies on the practice of wrapping or decorating loggers with 97 | // other loggers to provide composable pieces of functionality. It also means 98 | // that Logger.Log must return an error because some 99 | // implementations—especially those that output log data to an io.Writer—may 100 | // encounter errors that cannot be handled locally. This in turn means that 101 | // Loggers that wrap other loggers should return errors from the wrapped 102 | // logger up the stack. 103 | // 104 | // Fortunately, the decorator pattern also provides a way to avoid the 105 | // necessity to check for errors every time an application calls Logger.Log. 106 | // An application required to panic whenever its Logger encounters 107 | // an error could initialize its logger as follows. 108 | // 109 | // fmtlogger := log.NewLogfmtLogger(log.NewSyncWriter(os.Stdout)) 110 | // logger := log.LoggerFunc(func(keyvals ...interface{}) error { 111 | // if err := fmtlogger.Log(keyvals...); err != nil { 112 | // panic(err) 113 | // } 114 | // return nil 115 | // }) 116 | package log 117 | -------------------------------------------------------------------------------- /vendor/github.com/go-kit/kit/log/json_logger.go: -------------------------------------------------------------------------------- 1 | package log 2 | 3 | import ( 4 | "encoding" 5 | "encoding/json" 6 | "fmt" 7 | "io" 8 | "reflect" 9 | ) 10 | 11 | type jsonLogger struct { 12 | io.Writer 13 | } 14 | 15 | // NewJSONLogger returns a Logger that encodes keyvals to the Writer as a 16 | // single JSON object. Each log event produces no more than one call to 17 | // w.Write. The passed Writer must be safe for concurrent use by multiple 18 | // goroutines if the returned Logger will be used concurrently. 19 | func NewJSONLogger(w io.Writer) Logger { 20 | return &jsonLogger{w} 21 | } 22 | 23 | func (l *jsonLogger) Log(keyvals ...interface{}) error { 24 | n := (len(keyvals) + 1) / 2 // +1 to handle case when len is odd 25 | m := make(map[string]interface{}, n) 26 | for i := 0; i < len(keyvals); i += 2 { 27 | k := keyvals[i] 28 | var v interface{} = ErrMissingValue 29 | if i+1 < len(keyvals) { 30 | v = keyvals[i+1] 31 | } 32 | merge(m, k, v) 33 | } 34 | enc := json.NewEncoder(l.Writer) 35 | enc.SetEscapeHTML(false) 36 | return enc.Encode(m) 37 | } 38 | 39 | func merge(dst map[string]interface{}, k, v interface{}) { 40 | var key string 41 | switch x := k.(type) { 42 | case string: 43 | key = x 44 | case fmt.Stringer: 45 | key = safeString(x) 46 | default: 47 | key = fmt.Sprint(x) 48 | } 49 | 50 | // We want json.Marshaler and encoding.TextMarshaller to take priority over 51 | // err.Error() and v.String(). But json.Marshall (called later) does that by 52 | // default so we force a no-op if it's one of those 2 case. 53 | switch x := v.(type) { 54 | case json.Marshaler: 55 | case encoding.TextMarshaler: 56 | case error: 57 | v = safeError(x) 58 | case fmt.Stringer: 59 | v = safeString(x) 60 | } 61 | 62 | dst[key] = v 63 | } 64 | 65 | func safeString(str fmt.Stringer) (s string) { 66 | defer func() { 67 | if panicVal := recover(); panicVal != nil { 68 | if v := reflect.ValueOf(str); v.Kind() == reflect.Ptr && v.IsNil() { 69 | s = "NULL" 70 | } else { 71 | panic(panicVal) 72 | } 73 | } 74 | }() 75 | s = str.String() 76 | return 77 | } 78 | 79 | func safeError(err error) (s interface{}) { 80 | defer func() { 81 | if panicVal := recover(); panicVal != nil { 82 | if v := reflect.ValueOf(err); v.Kind() == reflect.Ptr && v.IsNil() { 83 | s = nil 84 | } else { 85 | panic(panicVal) 86 | } 87 | } 88 | }() 89 | s = err.Error() 90 | return 91 | } 92 | -------------------------------------------------------------------------------- /vendor/github.com/go-kit/kit/log/level/doc.go: -------------------------------------------------------------------------------- 1 | // Package level implements leveled logging on top of Go kit's log package. To 2 | // use the level package, create a logger as per normal in your func main, and 3 | // wrap it with level.NewFilter. 4 | // 5 | // var logger log.Logger 6 | // logger = log.NewLogfmtLogger(os.Stderr) 7 | // logger = level.NewFilter(logger, level.AllowInfo()) // <-- 8 | // logger = log.With(logger, "ts", log.DefaultTimestampUTC) 9 | // 10 | // Then, at the callsites, use one of the level.Debug, Info, Warn, or Error 11 | // helper methods to emit leveled log events. 12 | // 13 | // logger.Log("foo", "bar") // as normal, no level 14 | // level.Debug(logger).Log("request_id", reqID, "trace_data", trace.Get()) 15 | // if value > 100 { 16 | // level.Error(logger).Log("value", value) 17 | // } 18 | // 19 | // NewFilter allows precise control over what happens when a log event is 20 | // emitted without a level key, or if a squelched level is used. Check the 21 | // Option functions for details. 22 | package level 23 | -------------------------------------------------------------------------------- /vendor/github.com/go-kit/kit/log/level/level.go: -------------------------------------------------------------------------------- 1 | package level 2 | 3 | import "github.com/go-kit/kit/log" 4 | 5 | // Error returns a logger that includes a Key/ErrorValue pair. 6 | func Error(logger log.Logger) log.Logger { 7 | return log.WithPrefix(logger, Key(), ErrorValue()) 8 | } 9 | 10 | // Warn returns a logger that includes a Key/WarnValue pair. 11 | func Warn(logger log.Logger) log.Logger { 12 | return log.WithPrefix(logger, Key(), WarnValue()) 13 | } 14 | 15 | // Info returns a logger that includes a Key/InfoValue pair. 16 | func Info(logger log.Logger) log.Logger { 17 | return log.WithPrefix(logger, Key(), InfoValue()) 18 | } 19 | 20 | // Debug returns a logger that includes a Key/DebugValue pair. 21 | func Debug(logger log.Logger) log.Logger { 22 | return log.WithPrefix(logger, Key(), DebugValue()) 23 | } 24 | 25 | // NewFilter wraps next and implements level filtering. See the commentary on 26 | // the Option functions for a detailed description of how to configure levels. 27 | // If no options are provided, all leveled log events created with Debug, 28 | // Info, Warn or Error helper methods are squelched and non-leveled log 29 | // events are passed to next unmodified. 30 | func NewFilter(next log.Logger, options ...Option) log.Logger { 31 | l := &logger{ 32 | next: next, 33 | } 34 | for _, option := range options { 35 | option(l) 36 | } 37 | return l 38 | } 39 | 40 | type logger struct { 41 | next log.Logger 42 | allowed level 43 | squelchNoLevel bool 44 | errNotAllowed error 45 | errNoLevel error 46 | } 47 | 48 | func (l *logger) Log(keyvals ...interface{}) error { 49 | var hasLevel, levelAllowed bool 50 | for i := 1; i < len(keyvals); i += 2 { 51 | if v, ok := keyvals[i].(*levelValue); ok { 52 | hasLevel = true 53 | levelAllowed = l.allowed&v.level != 0 54 | break 55 | } 56 | } 57 | if !hasLevel && l.squelchNoLevel { 58 | return l.errNoLevel 59 | } 60 | if hasLevel && !levelAllowed { 61 | return l.errNotAllowed 62 | } 63 | return l.next.Log(keyvals...) 64 | } 65 | 66 | // Option sets a parameter for the leveled logger. 67 | type Option func(*logger) 68 | 69 | // AllowAll is an alias for AllowDebug. 70 | func AllowAll() Option { 71 | return AllowDebug() 72 | } 73 | 74 | // AllowDebug allows error, warn, info and debug level log events to pass. 75 | func AllowDebug() Option { 76 | return allowed(levelError | levelWarn | levelInfo | levelDebug) 77 | } 78 | 79 | // AllowInfo allows error, warn and info level log events to pass. 80 | func AllowInfo() Option { 81 | return allowed(levelError | levelWarn | levelInfo) 82 | } 83 | 84 | // AllowWarn allows error and warn level log events to pass. 85 | func AllowWarn() Option { 86 | return allowed(levelError | levelWarn) 87 | } 88 | 89 | // AllowError allows only error level log events to pass. 90 | func AllowError() Option { 91 | return allowed(levelError) 92 | } 93 | 94 | // AllowNone allows no leveled log events to pass. 95 | func AllowNone() Option { 96 | return allowed(0) 97 | } 98 | 99 | func allowed(allowed level) Option { 100 | return func(l *logger) { l.allowed = allowed } 101 | } 102 | 103 | // ErrNotAllowed sets the error to return from Log when it squelches a log 104 | // event disallowed by the configured Allow[Level] option. By default, 105 | // ErrNotAllowed is nil; in this case the log event is squelched with no 106 | // error. 107 | func ErrNotAllowed(err error) Option { 108 | return func(l *logger) { l.errNotAllowed = err } 109 | } 110 | 111 | // SquelchNoLevel instructs Log to squelch log events with no level, so that 112 | // they don't proceed through to the wrapped logger. If SquelchNoLevel is set 113 | // to true and a log event is squelched in this way, the error value 114 | // configured with ErrNoLevel is returned to the caller. 115 | func SquelchNoLevel(squelch bool) Option { 116 | return func(l *logger) { l.squelchNoLevel = squelch } 117 | } 118 | 119 | // ErrNoLevel sets the error to return from Log when it squelches a log event 120 | // with no level. By default, ErrNoLevel is nil; in this case the log event is 121 | // squelched with no error. 122 | func ErrNoLevel(err error) Option { 123 | return func(l *logger) { l.errNoLevel = err } 124 | } 125 | 126 | // NewInjector wraps next and returns a logger that adds a Key/level pair to 127 | // the beginning of log events that don't already contain a level. In effect, 128 | // this gives a default level to logs without a level. 129 | func NewInjector(next log.Logger, level Value) log.Logger { 130 | return &injector{ 131 | next: next, 132 | level: level, 133 | } 134 | } 135 | 136 | type injector struct { 137 | next log.Logger 138 | level interface{} 139 | } 140 | 141 | func (l *injector) Log(keyvals ...interface{}) error { 142 | for i := 1; i < len(keyvals); i += 2 { 143 | if _, ok := keyvals[i].(*levelValue); ok { 144 | return l.next.Log(keyvals...) 145 | } 146 | } 147 | kvs := make([]interface{}, len(keyvals)+2) 148 | kvs[0], kvs[1] = key, l.level 149 | copy(kvs[2:], keyvals) 150 | return l.next.Log(kvs...) 151 | } 152 | 153 | // Value is the interface that each of the canonical level values implement. 154 | // It contains unexported methods that prevent types from other packages from 155 | // implementing it and guaranteeing that NewFilter can distinguish the levels 156 | // defined in this package from all other values. 157 | type Value interface { 158 | String() string 159 | levelVal() 160 | } 161 | 162 | // Key returns the unique key added to log events by the loggers in this 163 | // package. 164 | func Key() interface{} { return key } 165 | 166 | // ErrorValue returns the unique value added to log events by Error. 167 | func ErrorValue() Value { return errorValue } 168 | 169 | // WarnValue returns the unique value added to log events by Warn. 170 | func WarnValue() Value { return warnValue } 171 | 172 | // InfoValue returns the unique value added to log events by Info. 173 | func InfoValue() Value { return infoValue } 174 | 175 | // DebugValue returns the unique value added to log events by Warn. 176 | func DebugValue() Value { return debugValue } 177 | 178 | var ( 179 | // key is of type interface{} so that it allocates once during package 180 | // initialization and avoids allocating every time the value is added to a 181 | // []interface{} later. 182 | key interface{} = "level" 183 | 184 | errorValue = &levelValue{level: levelError, name: "error"} 185 | warnValue = &levelValue{level: levelWarn, name: "warn"} 186 | infoValue = &levelValue{level: levelInfo, name: "info"} 187 | debugValue = &levelValue{level: levelDebug, name: "debug"} 188 | ) 189 | 190 | type level byte 191 | 192 | const ( 193 | levelDebug level = 1 << iota 194 | levelInfo 195 | levelWarn 196 | levelError 197 | ) 198 | 199 | type levelValue struct { 200 | name string 201 | level 202 | } 203 | 204 | func (v *levelValue) String() string { return v.name } 205 | func (v *levelValue) levelVal() {} 206 | -------------------------------------------------------------------------------- /vendor/github.com/go-kit/kit/log/log.go: -------------------------------------------------------------------------------- 1 | package log 2 | 3 | import "errors" 4 | 5 | // Logger is the fundamental interface for all log operations. Log creates a 6 | // log event from keyvals, a variadic sequence of alternating keys and values. 7 | // Implementations must be safe for concurrent use by multiple goroutines. In 8 | // particular, any implementation of Logger that appends to keyvals or 9 | // modifies or retains any of its elements must make a copy first. 10 | type Logger interface { 11 | Log(keyvals ...interface{}) error 12 | } 13 | 14 | // ErrMissingValue is appended to keyvals slices with odd length to substitute 15 | // the missing value. 16 | var ErrMissingValue = errors.New("(MISSING)") 17 | 18 | // With returns a new contextual logger with keyvals prepended to those passed 19 | // to calls to Log. If logger is also a contextual logger created by With or 20 | // WithPrefix, keyvals is appended to the existing context. 21 | // 22 | // The returned Logger replaces all value elements (odd indexes) containing a 23 | // Valuer with their generated value for each call to its Log method. 24 | func With(logger Logger, keyvals ...interface{}) Logger { 25 | if len(keyvals) == 0 { 26 | return logger 27 | } 28 | l := newContext(logger) 29 | kvs := append(l.keyvals, keyvals...) 30 | if len(kvs)%2 != 0 { 31 | kvs = append(kvs, ErrMissingValue) 32 | } 33 | return &context{ 34 | logger: l.logger, 35 | // Limiting the capacity of the stored keyvals ensures that a new 36 | // backing array is created if the slice must grow in Log or With. 37 | // Using the extra capacity without copying risks a data race that 38 | // would violate the Logger interface contract. 39 | keyvals: kvs[:len(kvs):len(kvs)], 40 | hasValuer: l.hasValuer || containsValuer(keyvals), 41 | } 42 | } 43 | 44 | // WithPrefix returns a new contextual logger with keyvals prepended to those 45 | // passed to calls to Log. If logger is also a contextual logger created by 46 | // With or WithPrefix, keyvals is prepended to the existing context. 47 | // 48 | // The returned Logger replaces all value elements (odd indexes) containing a 49 | // Valuer with their generated value for each call to its Log method. 50 | func WithPrefix(logger Logger, keyvals ...interface{}) Logger { 51 | if len(keyvals) == 0 { 52 | return logger 53 | } 54 | l := newContext(logger) 55 | // Limiting the capacity of the stored keyvals ensures that a new 56 | // backing array is created if the slice must grow in Log or With. 57 | // Using the extra capacity without copying risks a data race that 58 | // would violate the Logger interface contract. 59 | n := len(l.keyvals) + len(keyvals) 60 | if len(keyvals)%2 != 0 { 61 | n++ 62 | } 63 | kvs := make([]interface{}, 0, n) 64 | kvs = append(kvs, keyvals...) 65 | if len(kvs)%2 != 0 { 66 | kvs = append(kvs, ErrMissingValue) 67 | } 68 | kvs = append(kvs, l.keyvals...) 69 | return &context{ 70 | logger: l.logger, 71 | keyvals: kvs, 72 | hasValuer: l.hasValuer || containsValuer(keyvals), 73 | } 74 | } 75 | 76 | // context is the Logger implementation returned by With and WithPrefix. It 77 | // wraps a Logger and holds keyvals that it includes in all log events. Its 78 | // Log method calls bindValues to generate values for each Valuer in the 79 | // context keyvals. 80 | // 81 | // A context must always have the same number of stack frames between calls to 82 | // its Log method and the eventual binding of Valuers to their value. This 83 | // requirement comes from the functional requirement to allow a context to 84 | // resolve application call site information for a Caller stored in the 85 | // context. To do this we must be able to predict the number of logging 86 | // functions on the stack when bindValues is called. 87 | // 88 | // Two implementation details provide the needed stack depth consistency. 89 | // 90 | // 1. newContext avoids introducing an additional layer when asked to 91 | // wrap another context. 92 | // 2. With and WithPrefix avoid introducing an additional layer by 93 | // returning a newly constructed context with a merged keyvals rather 94 | // than simply wrapping the existing context. 95 | type context struct { 96 | logger Logger 97 | keyvals []interface{} 98 | hasValuer bool 99 | } 100 | 101 | func newContext(logger Logger) *context { 102 | if c, ok := logger.(*context); ok { 103 | return c 104 | } 105 | return &context{logger: logger} 106 | } 107 | 108 | // Log replaces all value elements (odd indexes) containing a Valuer in the 109 | // stored context with their generated value, appends keyvals, and passes the 110 | // result to the wrapped Logger. 111 | func (l *context) Log(keyvals ...interface{}) error { 112 | kvs := append(l.keyvals, keyvals...) 113 | if len(kvs)%2 != 0 { 114 | kvs = append(kvs, ErrMissingValue) 115 | } 116 | if l.hasValuer { 117 | // If no keyvals were appended above then we must copy l.keyvals so 118 | // that future log events will reevaluate the stored Valuers. 119 | if len(keyvals) == 0 { 120 | kvs = append([]interface{}{}, l.keyvals...) 121 | } 122 | bindValues(kvs[:len(l.keyvals)]) 123 | } 124 | return l.logger.Log(kvs...) 125 | } 126 | 127 | // LoggerFunc is an adapter to allow use of ordinary functions as Loggers. If 128 | // f is a function with the appropriate signature, LoggerFunc(f) is a Logger 129 | // object that calls f. 130 | type LoggerFunc func(...interface{}) error 131 | 132 | // Log implements Logger by calling f(keyvals...). 133 | func (f LoggerFunc) Log(keyvals ...interface{}) error { 134 | return f(keyvals...) 135 | } 136 | -------------------------------------------------------------------------------- /vendor/github.com/go-kit/kit/log/logfmt_logger.go: -------------------------------------------------------------------------------- 1 | package log 2 | 3 | import ( 4 | "bytes" 5 | "io" 6 | "sync" 7 | 8 | "github.com/go-logfmt/logfmt" 9 | ) 10 | 11 | type logfmtEncoder struct { 12 | *logfmt.Encoder 13 | buf bytes.Buffer 14 | } 15 | 16 | func (l *logfmtEncoder) Reset() { 17 | l.Encoder.Reset() 18 | l.buf.Reset() 19 | } 20 | 21 | var logfmtEncoderPool = sync.Pool{ 22 | New: func() interface{} { 23 | var enc logfmtEncoder 24 | enc.Encoder = logfmt.NewEncoder(&enc.buf) 25 | return &enc 26 | }, 27 | } 28 | 29 | type logfmtLogger struct { 30 | w io.Writer 31 | } 32 | 33 | // NewLogfmtLogger returns a logger that encodes keyvals to the Writer in 34 | // logfmt format. Each log event produces no more than one call to w.Write. 35 | // The passed Writer must be safe for concurrent use by multiple goroutines if 36 | // the returned Logger will be used concurrently. 37 | func NewLogfmtLogger(w io.Writer) Logger { 38 | return &logfmtLogger{w} 39 | } 40 | 41 | func (l logfmtLogger) Log(keyvals ...interface{}) error { 42 | enc := logfmtEncoderPool.Get().(*logfmtEncoder) 43 | enc.Reset() 44 | defer logfmtEncoderPool.Put(enc) 45 | 46 | if err := enc.EncodeKeyvals(keyvals...); err != nil { 47 | return err 48 | } 49 | 50 | // Add newline to the end of the buffer 51 | if err := enc.EndRecord(); err != nil { 52 | return err 53 | } 54 | 55 | // The Logger interface requires implementations to be safe for concurrent 56 | // use by multiple goroutines. For this implementation that means making 57 | // only one call to l.w.Write() for each call to Log. 58 | if _, err := l.w.Write(enc.buf.Bytes()); err != nil { 59 | return err 60 | } 61 | return nil 62 | } 63 | -------------------------------------------------------------------------------- /vendor/github.com/go-kit/kit/log/nop_logger.go: -------------------------------------------------------------------------------- 1 | package log 2 | 3 | type nopLogger struct{} 4 | 5 | // NewNopLogger returns a logger that doesn't do anything. 6 | func NewNopLogger() Logger { return nopLogger{} } 7 | 8 | func (nopLogger) Log(...interface{}) error { return nil } 9 | -------------------------------------------------------------------------------- /vendor/github.com/go-kit/kit/log/stdlib.go: -------------------------------------------------------------------------------- 1 | package log 2 | 3 | import ( 4 | "io" 5 | "log" 6 | "regexp" 7 | "strings" 8 | ) 9 | 10 | // StdlibWriter implements io.Writer by invoking the stdlib log.Print. It's 11 | // designed to be passed to a Go kit logger as the writer, for cases where 12 | // it's necessary to redirect all Go kit log output to the stdlib logger. 13 | // 14 | // If you have any choice in the matter, you shouldn't use this. Prefer to 15 | // redirect the stdlib log to the Go kit logger via NewStdlibAdapter. 16 | type StdlibWriter struct{} 17 | 18 | // Write implements io.Writer. 19 | func (w StdlibWriter) Write(p []byte) (int, error) { 20 | log.Print(strings.TrimSpace(string(p))) 21 | return len(p), nil 22 | } 23 | 24 | // StdlibAdapter wraps a Logger and allows it to be passed to the stdlib 25 | // logger's SetOutput. It will extract date/timestamps, filenames, and 26 | // messages, and place them under relevant keys. 27 | type StdlibAdapter struct { 28 | Logger 29 | timestampKey string 30 | fileKey string 31 | messageKey string 32 | } 33 | 34 | // StdlibAdapterOption sets a parameter for the StdlibAdapter. 35 | type StdlibAdapterOption func(*StdlibAdapter) 36 | 37 | // TimestampKey sets the key for the timestamp field. By default, it's "ts". 38 | func TimestampKey(key string) StdlibAdapterOption { 39 | return func(a *StdlibAdapter) { a.timestampKey = key } 40 | } 41 | 42 | // FileKey sets the key for the file and line field. By default, it's "caller". 43 | func FileKey(key string) StdlibAdapterOption { 44 | return func(a *StdlibAdapter) { a.fileKey = key } 45 | } 46 | 47 | // MessageKey sets the key for the actual log message. By default, it's "msg". 48 | func MessageKey(key string) StdlibAdapterOption { 49 | return func(a *StdlibAdapter) { a.messageKey = key } 50 | } 51 | 52 | // NewStdlibAdapter returns a new StdlibAdapter wrapper around the passed 53 | // logger. It's designed to be passed to log.SetOutput. 54 | func NewStdlibAdapter(logger Logger, options ...StdlibAdapterOption) io.Writer { 55 | a := StdlibAdapter{ 56 | Logger: logger, 57 | timestampKey: "ts", 58 | fileKey: "caller", 59 | messageKey: "msg", 60 | } 61 | for _, option := range options { 62 | option(&a) 63 | } 64 | return a 65 | } 66 | 67 | func (a StdlibAdapter) Write(p []byte) (int, error) { 68 | result := subexps(p) 69 | keyvals := []interface{}{} 70 | var timestamp string 71 | if date, ok := result["date"]; ok && date != "" { 72 | timestamp = date 73 | } 74 | if time, ok := result["time"]; ok && time != "" { 75 | if timestamp != "" { 76 | timestamp += " " 77 | } 78 | timestamp += time 79 | } 80 | if timestamp != "" { 81 | keyvals = append(keyvals, a.timestampKey, timestamp) 82 | } 83 | if file, ok := result["file"]; ok && file != "" { 84 | keyvals = append(keyvals, a.fileKey, file) 85 | } 86 | if msg, ok := result["msg"]; ok { 87 | keyvals = append(keyvals, a.messageKey, msg) 88 | } 89 | if err := a.Logger.Log(keyvals...); err != nil { 90 | return 0, err 91 | } 92 | return len(p), nil 93 | } 94 | 95 | const ( 96 | logRegexpDate = `(?P[0-9]{4}/[0-9]{2}/[0-9]{2})?[ ]?` 97 | logRegexpTime = `(?P