diff --git a/.gitignore b/.gitignore
index 944a545..ec0e459 100644
--- a/.gitignore
+++ b/.gitignore
@@ -14,3 +14,6 @@ github_actions_exporter
# Output of the go coverage tool, specifically when used with LiteIDE
*.out
+
+# VSCode
+.vscode/
diff --git a/Makefile.common b/Makefile.common
index 7f0a09b..15f40a1 100644
--- a/Makefile.common
+++ b/Makefile.common
@@ -83,7 +83,7 @@ PROMU_URL := https://github.com/prometheus/promu/releases/download/v$(PROMU_
GOLANGCI_LINT :=
GOLANGCI_LINT_OPTS ?=
-GOLANGCI_LINT_VERSION ?= v1.33.0
+GOLANGCI_LINT_VERSION ?= v1.45.2
# golangci-lint only supports linux, darwin and windows platforms on i386/amd64.
# windows isn't included here because of the path separator being different.
ifeq ($(GOHOSTOS),$(filter $(GOHOSTOS),linux darwin))
diff --git a/collector.go b/collector.go
deleted file mode 100644
index e5d40e0..0000000
--- a/collector.go
+++ /dev/null
@@ -1,120 +0,0 @@
-package main
-
-import (
- "context"
-
- "github.com/google/go-github/v33/github"
- "github.com/prometheus/client_golang/prometheus"
-)
-
-var (
- histogramVec = prometheus.NewHistogramVec(prometheus.HistogramOpts{
- Name: "workflow_execution_time_seconds",
- Help: "Time that a workflow took to run.",
- Buckets: prometheus.ExponentialBuckets(1, 1.4, 30),
- },
- []string{"org", "repo", "workflow_name"},
- )
-
- totalMinutesUsedActions = prometheus.NewGaugeVec(prometheus.GaugeOpts{
- Name: "actions_total_minutes_used_minutes",
- Help: "Total minutes used for the GitHub Actions.",
- },
- []string{"org", "user"},
- )
-
- includedMinutesUsedActions = prometheus.NewGaugeVec(prometheus.GaugeOpts{
- Name: "actions_included_minutes",
- Help: "Included Minutes for the GitHub Actions.",
- },
- []string{"org", "user"},
- )
-
- totalPaidMinutesActions = prometheus.NewGaugeVec(prometheus.GaugeOpts{
- Name: "actions_total_paid_minutes",
- Help: "Paid Minutes for the GitHub Actions.",
- },
- []string{"org", "user"},
- )
-
- totalMinutesUsedUbuntuActions = prometheus.NewGaugeVec(prometheus.GaugeOpts{
- Name: "actions_total_minutes_used_ubuntu_minutes",
- Help: "Total minutes used for Ubuntu type for the GitHub Actions.",
- },
- []string{"org", "user"},
- )
-
- totalMinutesUsedMacOSActions = prometheus.NewGaugeVec(prometheus.GaugeOpts{
- Name: "actions_total_minutes_used_macos_minutes",
- Help: "Total minutes used for MacOS type for the GitHub Actions.",
- },
- []string{"org", "user"},
- )
-
- totalMinutesUsedWindowsActions = prometheus.NewGaugeVec(prometheus.GaugeOpts{
- Name: "actions_total_minutes_used_windows_minutes",
- Help: "Total minutes used for Windows type for the GitHub Actions.",
- },
- []string{"org", "user"},
- )
-)
-
-func init() {
- //Register metrics with prometheus
- prometheus.MustRegister(histogramVec)
- prometheus.MustRegister(totalMinutesUsedActions)
- prometheus.MustRegister(includedMinutesUsedActions)
- prometheus.MustRegister(totalPaidMinutesActions)
- prometheus.MustRegister(totalMinutesUsedUbuntuActions)
- prometheus.MustRegister(totalMinutesUsedMacOSActions)
- prometheus.MustRegister(totalMinutesUsedWindowsActions)
-}
-
-// CollectWorkflowRun collect the workflow execution run metric
-func CollectWorkflowRun(checkRunEvent *github.CheckRunEvent) {
- if checkRunEvent.GetCheckRun().GetStatus() != "completed" {
- return
- }
-
- workflowName := checkRunEvent.GetCheckRun().GetName()
- repo := checkRunEvent.GetRepo().GetName()
- org := checkRunEvent.GetRepo().GetOwner().GetLogin()
- endTime := checkRunEvent.GetCheckRun().GetCompletedAt()
- startTime := checkRunEvent.GetCheckRun().GetStartedAt()
- executionTime := endTime.Sub(startTime.Time).Seconds()
-
- histogramVec.WithLabelValues(org, repo, workflowName).Observe(executionTime)
-}
-
-// CollectActionBilling collect the action billing.
-func (c *GHActionExporter) CollectActionBilling() {
- if *gitHubOrg != "" {
- actionsBilling, _, err := c.GHClient.Billing.GetActionsBillingOrg(context.TODO(), *gitHubOrg)
- if err != nil {
- c.Logger.Log("msg", "failed to retrive the actions billing for an org", "org", *gitHubOrg, "err", err)
- return
- }
-
- totalMinutesUsedActions.WithLabelValues(*gitHubOrg, "").Set(float64(actionsBilling.TotalMinutesUsed))
- includedMinutesUsedActions.WithLabelValues(*gitHubOrg, "").Set(float64(actionsBilling.IncludedMinutes))
- totalPaidMinutesActions.WithLabelValues(*gitHubOrg, "").Set(float64(actionsBilling.TotalPaidMinutesUsed))
- totalMinutesUsedUbuntuActions.WithLabelValues(*gitHubOrg, "").Set(float64(actionsBilling.MinutesUsedBreakdown.Ubuntu))
- totalMinutesUsedMacOSActions.WithLabelValues(*gitHubOrg, "").Set(float64(actionsBilling.MinutesUsedBreakdown.MacOS))
- totalMinutesUsedWindowsActions.WithLabelValues(*gitHubOrg, "").Set(float64(actionsBilling.MinutesUsedBreakdown.Windows))
- }
-
- if *gitHubUser != "" {
- actionsBilling, _, err := c.GHClient.Billing.GetActionsBillingUser(context.TODO(), *gitHubUser)
- if err != nil {
- c.Logger.Log("msg", "failed to retrive the actions billing for an user", "user", *gitHubUser, "err", err)
- return
- }
-
- totalMinutesUsedActions.WithLabelValues("", *gitHubUser).Set(float64(actionsBilling.TotalMinutesUsed))
- includedMinutesUsedActions.WithLabelValues("", *gitHubUser).Set(float64(actionsBilling.IncludedMinutes))
- totalPaidMinutesActions.WithLabelValues("", *gitHubUser).Set(float64(actionsBilling.TotalPaidMinutesUsed))
- totalMinutesUsedUbuntuActions.WithLabelValues("", *gitHubUser).Set(float64(actionsBilling.MinutesUsedBreakdown.Ubuntu))
- totalMinutesUsedMacOSActions.WithLabelValues("", *gitHubUser).Set(float64(actionsBilling.MinutesUsedBreakdown.MacOS))
- totalMinutesUsedWindowsActions.WithLabelValues("", *gitHubUser).Set(float64(actionsBilling.MinutesUsedBreakdown.Windows))
- }
-}
diff --git a/go.mod b/go.mod
index bda22f2..5429fa4 100644
--- a/go.mod
+++ b/go.mod
@@ -4,12 +4,13 @@ go 1.15
require (
github.com/go-kit/kit v0.10.0
- github.com/google/go-cmp v0.5.1 // indirect
- github.com/google/go-github/v33 v33.0.1-0.20210219143306-5c87615fe927
+ github.com/google/go-github/v43 v43.0.0
github.com/prometheus/client_golang v1.8.0
github.com/prometheus/common v0.15.0
+ github.com/stretchr/testify v1.7.1
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
google.golang.org/protobuf v1.25.0 // indirect
gopkg.in/alecthomas/kingpin.v2 v2.2.6
+ gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
)
diff --git a/go.sum b/go.sum
index a3103fd..344f67b 100644
--- a/go.sum
+++ b/go.sum
@@ -27,6 +27,7 @@ github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+Ce
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
+github.com/bradleyfalzon/ghinstallation/v2 v2.0.4/go.mod h1:B40qPqJxWE0jDZgOR1JmaMy+4AY1eBP+IByOvqyAKp0=
github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ=
github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
@@ -73,6 +74,7 @@ github.com/gogo/googleapis v1.1.0/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFG
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
+github.com/golang-jwt/jwt/v4 v4.0.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
@@ -95,15 +97,17 @@ github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
-github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
-github.com/google/go-cmp v0.5.1 h1:JFrFEBb2xKufg6XkJsJr+WbKb4FQlURi5RUcBveYu9k=
-github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
-github.com/google/go-github/v33 v33.0.1-0.20210219143306-5c87615fe927 h1:GJWYiEEQLQ22a3BnB3nRFZt47xnVXKFUM9IdNfpKqi8=
-github.com/google/go-github/v33 v33.0.1-0.20210219143306-5c87615fe927/go.mod h1:GMdDnVZY/2TsWgp/lkYnpSAh6TrzhANBBwm6k6TTEXg=
-github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk=
-github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
+github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.7 h1:81/ik6ipDQS2aGcBfIN5dHDB36BwrStyeAQquSYCV4o=
+github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE=
+github.com/google/go-github/v41 v41.0.0/go.mod h1:XgmCA5H323A9rtgExdTcnDkcqp6S30AVACCBDOonIxg=
+github.com/google/go-github/v43 v43.0.0 h1:y+GL7LIsAIF2NZlJ46ZoC/D1W1ivZasT0lnWHMYPZ+U=
+github.com/google/go-github/v43 v43.0.0/go.mod h1:ZkTvvmCXBvsfPpTHXnH/d2hP9Y0cTbvN9kr5xqyXOIc=
+github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8=
+github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
@@ -152,7 +156,6 @@ github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
-github.com/konsorten/go-windows-terminal-sequences v1.0.3 h1:CE8S1cTafDpPvMhIxNJKvHsGVBgn1xWYf1NbHQhywc8=
github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
@@ -237,7 +240,6 @@ github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y8
github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
github.com/prometheus/common v0.7.0/go.mod h1:DjGbpBbp5NYNiECxcL/VnbXCCaQpKd3tt26CguLLsqA=
github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo=
-github.com/prometheus/common v0.14.0 h1:RHRyE8UocrbjU+6UvRzwi6HjiDfxrrBU91TtbKzkGp4=
github.com/prometheus/common v0.14.0/go.mod h1:U+gB1OBLb1lF3O42bTCL+FK18tX9Oar16Clt/msog/s=
github.com/prometheus/common v0.15.0 h1:4fgOnadei3EZvgRwxJ7RMpG1k1pOZth5Pc13tyspaKM=
github.com/prometheus/common v0.15.0/go.mod h1:U+gB1OBLb1lF3O42bTCL+FK18tX9Oar16Clt/msog/s=
@@ -258,7 +260,6 @@ github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
-github.com/sirupsen/logrus v1.6.0 h1:UBcNElsrwanuuMsnGSlYmtmgbb23qDR5dG+6X6Oo89I=
github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
@@ -273,8 +274,9 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
-github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
+github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY=
+github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
@@ -297,8 +299,9 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
-golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
+golang.org/x/crypto v0.0.0-20210817164053-32db794688a5 h1:HWj/xjIHfjYU5nVXpTM0s39J9CbLn7Cc5a7IC5rwsMQ=
+golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
@@ -323,8 +326,9 @@ golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
-golang.org/x/net v0.0.0-20200625001655-4c5254603344 h1:vGXIOMxbNfDTk/aXCmfdLgkrSV+Z2tcbze+pEc3v5W4=
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
+golang.org/x/net v0.0.0-20210226172049-e18ecbb05110 h1:qWPm9rbaAMKs8Bq/9LRpbMqxWRVUAQwMI9fVrssnTfw=
+golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421 h1:Wo7BWFiOk0QRFMLYMqJGFMd9CgUAcGx7V+qEg/h5IBI=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@@ -353,10 +357,14 @@ golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20201015000850-e3ed0017c211 h1:9UQO31fZ+0aKQOFldThf7BKPMJTiBfWycGh/u3UoO88=
golang.org/x/sys v0.0.0-20201015000850-e3ed0017c211/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1 h1:SrN+KX8Art/Sf4HNj6Zcz06G7VEz+7w9tdXTPOZ7+l4=
+golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
+golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
@@ -374,15 +382,15 @@ golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtn
golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
-golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
-google.golang.org/appengine v1.4.0 h1:/wp5JvzpHIxhs/dumFmF7BXTf3Z+dd4uXta4kVyO508=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
+google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c=
+google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
@@ -405,7 +413,6 @@ google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQ
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
-google.golang.org/protobuf v1.23.0 h1:4MY060fB1DLGMB/7MBTLnwQUY6+F09GEiz6SsrNqyzM=
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.25.0 h1:Ejskq+SyPohKW+1uil0JJMtmHCgJPJ/qWTxr8qp+R4c=
@@ -428,8 +435,10 @@ gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
-gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
+gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
diff --git a/internal/server/exporter.go b/internal/server/exporter.go
new file mode 100644
index 0000000..5568058
--- /dev/null
+++ b/internal/server/exporter.go
@@ -0,0 +1,257 @@
+package server
+
+import (
+ "bytes"
+ "context"
+ "crypto/hmac"
+ "crypto/sha1"
+ "encoding/hex"
+ "errors"
+ "fmt"
+ "io/ioutil"
+ "math"
+ "net/http"
+ "strings"
+
+ "github.com/cpanato/github_actions_exporter/model"
+ "github.com/go-kit/kit/log"
+ "github.com/go-kit/kit/log/level"
+ "github.com/google/go-github/v43/github"
+ "github.com/prometheus/client_golang/prometheus"
+ "golang.org/x/oauth2"
+)
+
+var (
+ workflowJobHistogramVec = prometheus.NewHistogramVec(prometheus.HistogramOpts{
+ Name: "workflow_job_duration_seconds",
+ Help: "Time that a workflow job took to reach a given state.",
+ Buckets: prometheus.ExponentialBuckets(1, 1.4, 30),
+ },
+ []string{"org", "repo", "state", "runner_group"},
+ )
+
+ histogramVec = prometheus.NewHistogramVec(prometheus.HistogramOpts{
+ Name: "workflow_execution_time_seconds",
+ Help: "Time that a workflow took to run.",
+ Buckets: prometheus.ExponentialBuckets(1, 1.4, 30),
+ },
+ []string{"org", "repo", "workflow_name"},
+ )
+
+ totalMinutesUsedActions = prometheus.NewGaugeVec(prometheus.GaugeOpts{
+ Name: "actions_total_minutes_used_minutes",
+ Help: "Total minutes used for the GitHub Actions.",
+ },
+ []string{"org", "user"},
+ )
+
+ includedMinutesUsedActions = prometheus.NewGaugeVec(prometheus.GaugeOpts{
+ Name: "actions_included_minutes",
+ Help: "Included Minutes for the GitHub Actions.",
+ },
+ []string{"org", "user"},
+ )
+
+ totalPaidMinutesActions = prometheus.NewGaugeVec(prometheus.GaugeOpts{
+ Name: "actions_total_paid_minutes",
+ Help: "Paid Minutes for the GitHub Actions.",
+ },
+ []string{"org", "user"},
+ )
+
+ totalMinutesUsedUbuntuActions = prometheus.NewGaugeVec(prometheus.GaugeOpts{
+ Name: "actions_total_minutes_used_ubuntu_minutes",
+ Help: "Total minutes used for Ubuntu type for the GitHub Actions.",
+ },
+ []string{"org", "user"},
+ )
+
+ totalMinutesUsedMacOSActions = prometheus.NewGaugeVec(prometheus.GaugeOpts{
+ Name: "actions_total_minutes_used_macos_minutes",
+ Help: "Total minutes used for MacOS type for the GitHub Actions.",
+ },
+ []string{"org", "user"},
+ )
+
+ totalMinutesUsedWindowsActions = prometheus.NewGaugeVec(prometheus.GaugeOpts{
+ Name: "actions_total_minutes_used_windows_minutes",
+ Help: "Total minutes used for Windows type for the GitHub Actions.",
+ },
+ []string{"org", "user"},
+ )
+)
+
+func init() {
+ //Register metrics with prometheus
+ prometheus.MustRegister(workflowJobHistogramVec)
+ prometheus.MustRegister(histogramVec)
+ prometheus.MustRegister(totalMinutesUsedActions)
+ prometheus.MustRegister(includedMinutesUsedActions)
+ prometheus.MustRegister(totalPaidMinutesActions)
+ prometheus.MustRegister(totalMinutesUsedUbuntuActions)
+ prometheus.MustRegister(totalMinutesUsedMacOSActions)
+ prometheus.MustRegister(totalMinutesUsedWindowsActions)
+}
+
+// GHActionExporter struct to hold some information
+type GHActionExporter struct {
+ GHClient *github.Client
+ Logger log.Logger
+ Opts ServerOpts
+ JobObserver WorkflowJobObserver
+}
+
+func NewGHActionExporter(logger log.Logger, opts ServerOpts) *GHActionExporter {
+ ctx := context.Background()
+ ts := oauth2.StaticTokenSource(
+ &oauth2.Token{AccessToken: opts.GitHubAPIToken},
+ )
+ tc := oauth2.NewClient(ctx, ts)
+ client := github.NewClient(tc)
+
+ return &GHActionExporter{
+ GHClient: client,
+ Logger: logger,
+ Opts: opts,
+ JobObserver: &JobObserver{},
+ }
+}
+
+// CollectActionBilling collect the action billing.
+func (c *GHActionExporter) CollectActionBilling() {
+ if c.Opts.GitHubOrg != "" {
+ actionsBilling, _, err := c.GHClient.Billing.GetActionsBillingOrg(context.TODO(), c.Opts.GitHubOrg)
+ if err != nil {
+ c.Logger.Log("msg", "failed to retrive the actions billing for an org", "org", c.Opts.GitHubOrg, "err", err)
+ return
+ }
+
+ totalMinutesUsedActions.WithLabelValues(c.Opts.GitHubOrg, "").Set(float64(actionsBilling.TotalMinutesUsed))
+ includedMinutesUsedActions.WithLabelValues(c.Opts.GitHubOrg, "").Set(float64(actionsBilling.IncludedMinutes))
+ totalPaidMinutesActions.WithLabelValues(c.Opts.GitHubOrg, "").Set(float64(actionsBilling.TotalPaidMinutesUsed))
+ totalMinutesUsedUbuntuActions.WithLabelValues(c.Opts.GitHubOrg, "").Set(float64(actionsBilling.MinutesUsedBreakdown.Ubuntu))
+ totalMinutesUsedMacOSActions.WithLabelValues(c.Opts.GitHubOrg, "").Set(float64(actionsBilling.MinutesUsedBreakdown.MacOS))
+ totalMinutesUsedWindowsActions.WithLabelValues(c.Opts.GitHubOrg, "").Set(float64(actionsBilling.MinutesUsedBreakdown.Windows))
+ }
+
+ if c.Opts.GitHubUser != "" {
+ actionsBilling, _, err := c.GHClient.Billing.GetActionsBillingUser(context.TODO(), c.Opts.GitHubUser)
+ if err != nil {
+ c.Logger.Log("msg", "failed to retrive the actions billing for an user", "user", c.Opts.GitHubUser, "err", err)
+ return
+ }
+
+ totalMinutesUsedActions.WithLabelValues("", c.Opts.GitHubUser).Set(float64(actionsBilling.TotalMinutesUsed))
+ includedMinutesUsedActions.WithLabelValues("", c.Opts.GitHubUser).Set(float64(actionsBilling.IncludedMinutes))
+ totalPaidMinutesActions.WithLabelValues("", c.Opts.GitHubUser).Set(float64(actionsBilling.TotalPaidMinutesUsed))
+ totalMinutesUsedUbuntuActions.WithLabelValues("", c.Opts.GitHubUser).Set(float64(actionsBilling.MinutesUsedBreakdown.Ubuntu))
+ totalMinutesUsedMacOSActions.WithLabelValues("", c.Opts.GitHubUser).Set(float64(actionsBilling.MinutesUsedBreakdown.MacOS))
+ totalMinutesUsedWindowsActions.WithLabelValues("", c.Opts.GitHubUser).Set(float64(actionsBilling.MinutesUsedBreakdown.Windows))
+ }
+}
+
+// handleGHWebHook responds to POST /gh_event, when receive a event from GitHub.
+func (c *GHActionExporter) HandleGHWebHook(w http.ResponseWriter, r *http.Request) {
+ buf, _ := ioutil.ReadAll(r.Body)
+
+ receivedHash := strings.SplitN(r.Header.Get("X-Hub-Signature"), "=", 2)
+ if receivedHash[0] != "sha1" {
+ level.Error(c.Logger).Log("msg", "invalid webhook hash signature: SHA1")
+ w.WriteHeader(http.StatusForbidden)
+ return
+ }
+
+ err := validateSignature(c.Opts.GitHubToken, receivedHash, buf)
+ if err != nil {
+ level.Error(c.Logger).Log("msg", "invalid token", "err", err)
+ w.WriteHeader(http.StatusForbidden)
+ return
+ }
+
+ eventType := r.Header.Get("X-GitHub-Event")
+ switch eventType {
+ case "ping":
+ pingEvent := model.PingEventFromJSON(ioutil.NopCloser(bytes.NewBuffer(buf)))
+ if pingEvent == nil {
+ level.Info(c.Logger).Log("msg", "ping event", "hookID", pingEvent.GetHookID())
+ w.WriteHeader(http.StatusBadRequest)
+ return
+ }
+ w.WriteHeader(http.StatusAccepted)
+ w.Write([]byte(`{"status": "honk"}`))
+ return
+ case "check_run":
+ event := model.CheckRunEventFromJSON(ioutil.NopCloser(bytes.NewBuffer(buf)))
+ level.Info(c.Logger).Log("msg", "got check_run event", "org", event.GetRepo().GetOwner().GetLogin(), "repo", event.GetRepo().GetName(), "workflowName", event.GetCheckRun().GetName())
+ go c.CollectWorkflowRun(event)
+ case "workflow_job":
+ event := model.WorkflowJobEventFromJSON(ioutil.NopCloser(bytes.NewBuffer(buf)))
+ level.Info(c.Logger).Log("msg", "got workflow_job event", "org", event.GetRepo().GetOwner().GetLogin(), "repo", event.GetRepo().GetName(), "runId", event.GetWorkflowJob().GetRunID())
+ go c.CollectWorkflowJobEvent(event)
+ default:
+ level.Info(c.Logger).Log("msg", "not implemented")
+ w.WriteHeader(http.StatusNotImplemented)
+ return
+ }
+
+ go c.CollectActionBilling()
+
+ w.Header().Set("Content-Type", "application/json")
+ w.WriteHeader(http.StatusAccepted)
+}
+
+// CollectWorkflowRun collect the workflow execution run metric
+func (c *GHActionExporter) CollectWorkflowRun(checkRunEvent *github.CheckRunEvent) {
+ if checkRunEvent.GetCheckRun().GetStatus() != "completed" {
+ return
+ }
+
+ workflowName := checkRunEvent.GetCheckRun().GetName()
+ repo := checkRunEvent.GetRepo().GetName()
+ org := checkRunEvent.GetRepo().GetOwner().GetLogin()
+ endTime := checkRunEvent.GetCheckRun().GetCompletedAt()
+ startTime := checkRunEvent.GetCheckRun().GetStartedAt()
+ executionTime := endTime.Sub(startTime.Time).Seconds()
+
+ histogramVec.WithLabelValues(org, repo, workflowName).Observe(executionTime)
+}
+
+func (c *GHActionExporter) CollectWorkflowJobEvent(event *github.WorkflowJobEvent) {
+ if event.GetAction() == "queued" {
+ return
+ }
+
+ repo := event.GetRepo().GetName()
+ org := event.GetRepo().GetOwner().GetLogin()
+ runnerGroup := event.WorkflowJob.GetRunnerGroupName()
+
+ if event.GetAction() == "in_progress" {
+ firstStep := event.WorkflowJob.Steps[0]
+ queuedSeconds := firstStep.StartedAt.Time.Sub(event.WorkflowJob.StartedAt.Time).Seconds()
+ c.JobObserver.ObserveWorkflowJobDuration(org, repo, "queued", runnerGroup, math.Max(0, queuedSeconds))
+ }
+
+ if event.GetAction() == "completed" && event.GetWorkflowJob().GetConclusion() != "skipped" {
+ firstStepStarted := event.WorkflowJob.Steps[0].StartedAt.Time
+ lastStepCompleted := event.WorkflowJob.Steps[len(event.WorkflowJob.Steps)-1].CompletedAt.Time
+ jobSeconds := lastStepCompleted.Sub(firstStepStarted).Seconds()
+ c.JobObserver.ObserveWorkflowJobDuration(org, repo, "in_progress", runnerGroup, math.Max(0, jobSeconds))
+ }
+}
+
+// validateSignature validate the incoming github event.
+func validateSignature(gitHubToken string, receivedHash []string, bodyBuffer []byte) error {
+ hash := hmac.New(sha1.New, []byte(gitHubToken))
+ if _, err := hash.Write(bodyBuffer); err != nil {
+ msg := fmt.Sprintf("Cannot compute the HMAC for request: %s\n", err)
+ return errors.New(msg)
+ }
+
+ expectedHash := hex.EncodeToString(hash.Sum(nil))
+ if receivedHash[1] != expectedHash {
+ msg := fmt.Sprintf("Expected Hash does not match the received hash: %s\n", expectedHash)
+ return errors.New(msg)
+ }
+
+ return nil
+}
diff --git a/internal/server/exporter_test.go b/internal/server/exporter_test.go
new file mode 100644
index 0000000..fda19d7
--- /dev/null
+++ b/internal/server/exporter_test.go
@@ -0,0 +1,424 @@
+package server_test
+
+import (
+ "bytes"
+ "crypto/hmac"
+ "crypto/sha1"
+ "encoding/hex"
+ "encoding/json"
+ "fmt"
+ "net/http"
+ "net/http/httptest"
+ "os"
+ "testing"
+ "time"
+
+ "github.com/cpanato/github_actions_exporter/internal/server"
+ "github.com/go-kit/kit/log"
+ "github.com/google/go-github/v43/github"
+ "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
+)
+
+const (
+ webhookSecret = "webhook-secret"
+)
+
+func Test_GHActionExporter_HandleGHWebHook_RejectsInvalidSignature(t *testing.T) {
+
+ // Given
+ subject := server.GHActionExporter{
+ Logger: log.NewLogfmtLogger(log.NewSyncWriter(os.Stderr)),
+ Opts: server.ServerOpts{
+ GitHubToken: webhookSecret,
+ },
+ }
+
+ req, err := http.NewRequest("POST", "/anything", bytes.NewReader(nil))
+ require.NoError(t, err)
+ req.Header.Add("X-Hub-Signature", "sha1=incorrect")
+
+ // When
+ res := httptest.NewRecorder()
+ subject.HandleGHWebHook(res, req)
+
+ // Then
+ assert.Equal(t, http.StatusForbidden, res.Result().StatusCode)
+}
+
+func Test_GHActionExporter_HandleGHWebHook_ValidatesValidSignature(t *testing.T) {
+
+ // Given
+ observer := NewTestJobObserver(t)
+ subject := server.GHActionExporter{
+ Logger: log.NewLogfmtLogger(log.NewSyncWriter(os.Stdout)),
+ Opts: server.ServerOpts{
+ GitHubToken: webhookSecret,
+ },
+ JobObserver: observer,
+ }
+
+ req, err := http.NewRequest("POST", "/anything", bytes.NewReader(nil))
+ require.NoError(t, err)
+ addValidSignatureHeader(t, req, nil)
+
+ // When
+ res := httptest.NewRecorder()
+ subject.HandleGHWebHook(res, req)
+
+ // Then
+ assert.Equal(t, http.StatusNotImplemented, res.Result().StatusCode)
+}
+
+func Test_GHActionExporter_HandleGHWebHook_Ping(t *testing.T) {
+
+ // Given
+ subject := server.GHActionExporter{
+ Logger: log.NewLogfmtLogger(log.NewSyncWriter(os.Stdout)),
+ Opts: server.ServerOpts{
+ GitHubToken: webhookSecret,
+ },
+ }
+ req := testWebhookRequest(t, "/anything", "ping", github.PingEvent{})
+
+ // When
+ res := httptest.NewRecorder()
+ subject.HandleGHWebHook(res, req)
+
+ // Then
+ assert.Equal(t, http.StatusAccepted, res.Result().StatusCode)
+ assert.Equal(t, `{"status": "honk"}`, res.Body.String())
+}
+
+func Test_GHActionExporter_HandleGHWebHook_CheckRun(t *testing.T) {
+
+ // Given
+ subject := server.GHActionExporter{
+ Logger: log.NewLogfmtLogger(log.NewSyncWriter(os.Stdout)),
+ Opts: server.ServerOpts{
+ GitHubToken: webhookSecret,
+ },
+ }
+ req := testWebhookRequest(t, "/anything", "check_run", github.CheckRun{})
+
+ // When
+ res := httptest.NewRecorder()
+ subject.HandleGHWebHook(res, req)
+
+ // Then
+ assert.Equal(t, http.StatusAccepted, res.Result().StatusCode)
+}
+
+func Test_GHActionExporter_HandleGHWebHook_WorkflowJobQueuedEvent(t *testing.T) {
+
+ // Given
+ observer := &testJobObserver{}
+ subject := server.GHActionExporter{
+ Logger: log.NewLogfmtLogger(log.NewSyncWriter(os.Stdout)),
+ Opts: server.ServerOpts{
+ GitHubToken: webhookSecret,
+ },
+ JobObserver: observer,
+ }
+ event := github.WorkflowJobEvent{
+ Action: github.String("queued"),
+ Repo: &github.Repository{
+ Name: github.String("some-repo"),
+ Owner: &github.User{
+ Login: github.String("someone"),
+ },
+ },
+ }
+ req := testWebhookRequest(t, "/anything", "workflow_job", event)
+
+ // When
+ res := httptest.NewRecorder()
+ subject.HandleGHWebHook(res, req)
+
+ // Then
+ assert.Equal(t, http.StatusAccepted, res.Result().StatusCode)
+ observer.assertNoObservation(1 * time.Second)
+}
+
+func Test_GHActionExporter_HandleGHWebHook_WorkflowJobInProgressEvent(t *testing.T) {
+
+ // Given
+ observer := NewTestJobObserver(t)
+ subject := server.GHActionExporter{
+ Logger: log.NewLogfmtLogger(log.NewSyncWriter(os.Stdout)),
+ Opts: server.ServerOpts{
+ GitHubToken: webhookSecret,
+ },
+ JobObserver: observer,
+ }
+
+ repo := "some-repo"
+ org := "someone"
+ expectedDuration := 10.0
+ jobStartedAt := time.Unix(1650308740, 0)
+ stepStartedAt := jobStartedAt.Add(time.Duration(expectedDuration) * time.Second)
+ runnerGroupName := "runner-group"
+
+ event := github.WorkflowJobEvent{
+ Action: github.String("in_progress"),
+ Repo: &github.Repository{
+ Name: &repo,
+ Owner: &github.User{
+ Login: &org,
+ },
+ },
+ WorkflowJob: &github.WorkflowJob{
+ StartedAt: &github.Timestamp{Time: jobStartedAt},
+ Steps: []*github.TaskStep{
+ {
+ StartedAt: &github.Timestamp{Time: stepStartedAt},
+ },
+ {
+ StartedAt: &github.Timestamp{Time: stepStartedAt.Add(5 * time.Second)},
+ },
+ },
+ RunnerGroupName: &runnerGroupName,
+ },
+ }
+ req := testWebhookRequest(t, "/anything", "workflow_job", event)
+
+ // When
+ res := httptest.NewRecorder()
+ subject.HandleGHWebHook(res, req)
+
+ // Then
+ assert.Equal(t, http.StatusAccepted, res.Result().StatusCode)
+ observer.assetObservation(jobObservation{
+ org: org,
+ repo: repo,
+ state: "queued",
+ runnerGroup: runnerGroupName,
+ seconds: expectedDuration,
+ }, 50*time.Millisecond)
+}
+
+func Test_GHActionExporter_HandleGHWebHook_WorkflowJobInProgressEventWithNegativeDuration(t *testing.T) {
+
+ // Given
+ observer := NewTestJobObserver(t)
+ subject := server.GHActionExporter{
+ Logger: log.NewLogfmtLogger(log.NewSyncWriter(os.Stdout)),
+ Opts: server.ServerOpts{
+ GitHubToken: webhookSecret,
+ },
+ JobObserver: observer,
+ }
+
+ repo := "some-repo"
+ org := "someone"
+ expectedDuration := 10.0
+ jobStartedAt := time.Unix(1650308740, 0)
+ stepStartedAt := jobStartedAt.Add(-1 * time.Duration(expectedDuration) * time.Second)
+ runnerGroupName := "runner-group"
+
+ event := github.WorkflowJobEvent{
+ Action: github.String("in_progress"),
+ Repo: &github.Repository{
+ Name: &repo,
+ Owner: &github.User{
+ Login: &org,
+ },
+ },
+ WorkflowJob: &github.WorkflowJob{
+ StartedAt: &github.Timestamp{Time: jobStartedAt},
+ Steps: []*github.TaskStep{
+ {
+ StartedAt: &github.Timestamp{Time: stepStartedAt},
+ },
+ {
+ StartedAt: &github.Timestamp{Time: stepStartedAt.Add(5 * time.Second)},
+ },
+ },
+ RunnerGroupName: &runnerGroupName,
+ },
+ }
+ req := testWebhookRequest(t, "/anything", "workflow_job", event)
+
+ // When
+ res := httptest.NewRecorder()
+ subject.HandleGHWebHook(res, req)
+
+ // Then
+ assert.Equal(t, http.StatusAccepted, res.Result().StatusCode)
+ observer.assetObservation(jobObservation{
+ org: org,
+ repo: repo,
+ state: "queued",
+ runnerGroup: runnerGroupName,
+ seconds: 0,
+ }, 50*time.Millisecond)
+}
+
+func Test_GHActionExporter_HandleGHWebHook_WorkflowJobCompletedEvent(t *testing.T) {
+
+ // Given
+ observer := NewTestJobObserver(t)
+ subject := server.GHActionExporter{
+ Logger: log.NewLogfmtLogger(log.NewSyncWriter(os.Stdout)),
+ Opts: server.ServerOpts{
+ GitHubToken: webhookSecret,
+ },
+ JobObserver: observer,
+ }
+
+ repo := "some-repo"
+ org := "someone"
+ expectedStepTime := 5.0
+
+ firstStepStartedAt := time.Unix(1650308740, 0)
+ lastStepStartedAt := firstStepStartedAt.Add(time.Duration(expectedStepTime) * time.Second)
+ lastStepFinishedAt := lastStepStartedAt.Add(time.Duration(expectedStepTime) * time.Second)
+ runnerGroupName := "runner-group"
+
+ event := github.WorkflowJobEvent{
+ Action: github.String("completed"),
+ Repo: &github.Repository{
+ Name: &repo,
+ Owner: &github.User{
+ Login: &org,
+ },
+ },
+ WorkflowJob: &github.WorkflowJob{
+ StartedAt: nil,
+ Steps: []*github.TaskStep{
+ {
+ Number: github.Int64(1),
+ StartedAt: &github.Timestamp{Time: firstStepStartedAt},
+ },
+ {
+ Number: github.Int64(2),
+ StartedAt: &github.Timestamp{Time: lastStepStartedAt},
+ CompletedAt: &github.Timestamp{Time: lastStepFinishedAt},
+ },
+ },
+ RunnerGroupName: &runnerGroupName,
+ },
+ }
+ req := testWebhookRequest(t, "/anything", "workflow_job", event)
+
+ // When
+ res := httptest.NewRecorder()
+ subject.HandleGHWebHook(res, req)
+
+ // Then
+ assert.Equal(t, http.StatusAccepted, res.Result().StatusCode)
+ observer.assetObservation(jobObservation{
+ org: org,
+ repo: repo,
+ state: "in_progress",
+ runnerGroup: runnerGroupName,
+ seconds: expectedStepTime * 2,
+ }, 50*time.Millisecond)
+}
+
+func Test_GHActionExporter_HandleGHWebHook_WorkflowJobCompletedEventWithSkippedConclusion(t *testing.T) {
+
+ // Given
+ observer := NewTestJobObserver(t)
+ subject := server.GHActionExporter{
+ Logger: log.NewLogfmtLogger(log.NewSyncWriter(os.Stdout)),
+ Opts: server.ServerOpts{
+ GitHubToken: webhookSecret,
+ },
+ JobObserver: observer,
+ }
+
+ repo := "some-repo"
+ org := "someone"
+ runnerGroupName := "runner-group"
+ event := github.WorkflowJobEvent{
+ Action: github.String("completed"),
+ Repo: &github.Repository{
+ Name: &repo,
+ Owner: &github.User{
+ Login: &org,
+ },
+ },
+ WorkflowJob: &github.WorkflowJob{
+ StartedAt: nil,
+ Conclusion: github.String("skipped"),
+ Steps: []*github.TaskStep{},
+ RunnerGroupName: &runnerGroupName,
+ },
+ }
+ req := testWebhookRequest(t, "/anything", "workflow_job", event)
+
+ // When
+ res := httptest.NewRecorder()
+ subject.HandleGHWebHook(res, req)
+
+ // Then
+ assert.Equal(t, http.StatusAccepted, res.Result().StatusCode)
+ observer.assertNoObservation(1 * time.Second)
+}
+
+func testWebhookRequest(t *testing.T, url, event string, payload interface{}) *http.Request {
+ b, err := json.Marshal(payload)
+ require.NoError(t, err)
+
+ req, err := http.NewRequest("POST", url, bytes.NewReader(b))
+ require.NoError(t, err)
+
+ addValidSignatureHeader(t, req, b)
+ req.Header.Add("X-GitHub-Event", event)
+ return req
+}
+
+func addValidSignatureHeader(t *testing.T, req *http.Request, payload []byte) {
+ h := hmac.New(sha1.New, []byte(webhookSecret))
+ _, err := h.Write(payload)
+ require.NoError(t, err)
+
+ req.Header.Add("X-Hub-Signature", fmt.Sprintf("sha1=%s", hex.EncodeToString(h.Sum(nil))))
+}
+
+type jobObservation struct {
+ org, repo, state, runnerGroup string
+ seconds float64
+}
+
+var _ server.WorkflowJobObserver = (*testJobObserver)(nil)
+
+type testJobObserver struct {
+ t *testing.T
+ observed chan jobObservation
+}
+
+func NewTestJobObserver(t *testing.T) *testJobObserver {
+ return &testJobObserver{
+ t: t,
+ observed: make(chan jobObservation, 1),
+ }
+}
+
+func (o *testJobObserver) ObserveWorkflowJobDuration(org, repo, state, runnerGroup string, seconds float64) {
+ o.observed <- jobObservation{
+ org: org,
+ repo: repo,
+ state: state,
+ runnerGroup: runnerGroup,
+ seconds: seconds,
+ }
+}
+
+func (o *testJobObserver) assertNoObservation(timeout time.Duration) {
+ select {
+ case <-time.After(timeout):
+ case <-o.observed:
+ o.t.Fatal("expected no observation but an observation occurred")
+ }
+}
+
+func (o *testJobObserver) assetObservation(expected jobObservation, timeout time.Duration) {
+ select {
+ case <-time.After(timeout):
+ o.t.Fatal("expected no observation but none occurred")
+ case observed := <-o.observed:
+ assert.Equal(o.t, expected, observed)
+ }
+}
diff --git a/internal/server/metrics.go b/internal/server/metrics.go
new file mode 100644
index 0000000..c25b1ec
--- /dev/null
+++ b/internal/server/metrics.go
@@ -0,0 +1,14 @@
+package server
+
+type WorkflowJobObserver interface {
+ ObserveWorkflowJobDuration(org, repo, state, runnerGroup string, seconds float64)
+}
+
+var _ WorkflowJobObserver = (*JobObserver)(nil)
+
+type JobObserver struct{}
+
+func (o *JobObserver) ObserveWorkflowJobDuration(org, repo, state, runnerGroup string, seconds float64) {
+ workflowJobHistogramVec.WithLabelValues(org, repo, state, runnerGroup).
+ Observe(seconds)
+}
diff --git a/internal/server/server.go b/internal/server/server.go
new file mode 100644
index 0000000..01af3c9
--- /dev/null
+++ b/internal/server/server.go
@@ -0,0 +1,127 @@
+package server
+
+import (
+ "context"
+ "errors"
+ "fmt"
+ "net"
+ "net/http"
+ "strings"
+
+ "github.com/go-kit/kit/log"
+ "github.com/go-kit/kit/log/level"
+ "github.com/prometheus/client_golang/prometheus/promhttp"
+ "github.com/prometheus/common/version"
+)
+
+type ServerOpts struct {
+ MetricsPath string
+ ListenAddress string
+ WebhookPath string
+ // GitHub webhook token.
+ GitHubToken string
+ // GitHub API token.
+ GitHubAPIToken string
+ GitHubOrg string
+ GitHubUser string
+}
+
+type Server struct {
+ logger log.Logger
+ server *http.Server
+ exporter *GHActionExporter
+ opts ServerOpts
+}
+
+func NewServer(logger log.Logger, opts ServerOpts) *Server {
+ mux := http.NewServeMux()
+
+ httpServer := &http.Server{
+ Handler: mux,
+ }
+
+ exporter := NewGHActionExporter(logger, opts)
+ server := &Server{
+ logger: logger,
+ server: httpServer,
+ exporter: exporter,
+ opts: opts,
+ }
+
+ mux.Handle(opts.MetricsPath, promhttp.Handler())
+ mux.HandleFunc(opts.WebhookPath, server.exporter.HandleGHWebHook)
+ mux.HandleFunc("/", server.handleRoot)
+
+ return server
+}
+
+func (s *Server) Serve(ctx context.Context) error {
+
+ listener, err := getListener(s.opts.ListenAddress, s.logger)
+ if err != nil {
+ return fmt.Errorf("get listener: %v", err)
+ }
+
+ level.Info(s.logger).Log("msg", "GitHub Actions Prometheus Exporter has successfully started")
+ err = s.server.Serve(listener)
+
+ if err != nil && !errors.Is(err, http.ErrServerClosed) {
+ return fmt.Errorf("server closed: %v", err)
+ }
+ return nil
+}
+
+func (s *Server) Shutdown(ctx context.Context) error {
+ return s.server.Shutdown(ctx)
+}
+
+func (s *Server) handleRoot(w http.ResponseWriter, _ *http.Request) {
+ w.Write([]byte(`
+
GitHub Actions Exporter
+
+ GitHub Actions Exporter
+ ` + version.Print("ghactions_exporter") + `
+ Metrics
+
+
+ `))
+}
+
+func getListener(listenAddress string, logger log.Logger) (net.Listener, error) {
+ var listener net.Listener
+ var err error
+
+ if strings.HasPrefix(listenAddress, "unix:") {
+ path, _, pathError := parseUnixSocketAddress(listenAddress)
+ if pathError != nil {
+ return listener, fmt.Errorf("parsing unix domain socket listen address %s failed: %v", listenAddress, pathError)
+ }
+ listener, err = net.ListenUnix("unix", &net.UnixAddr{Name: path, Net: "unix"})
+ } else {
+ listener, err = net.Listen("tcp", listenAddress)
+ }
+
+ if err != nil {
+ return listener, err
+ }
+
+ level.Info(logger).Log("msg", fmt.Sprintf("Listening on %s", listenAddress))
+ return listener, nil
+}
+
+func parseUnixSocketAddress(address string) (string, string, error) {
+ addressParts := strings.Split(address, ":")
+ addressPartsLength := len(addressParts)
+
+ if addressPartsLength > 3 || addressPartsLength < 1 {
+ return "", "", fmt.Errorf("address for unix domain socket has wrong format")
+ }
+
+ unixSocketPath := addressParts[1]
+ requestPath := ""
+ if addressPartsLength == 3 {
+ requestPath = addressParts[2]
+ }
+
+ return unixSocketPath, requestPath, nil
+}
diff --git a/internal/server/server_test.go b/internal/server/server_test.go
new file mode 100644
index 0000000..2b15f42
--- /dev/null
+++ b/internal/server/server_test.go
@@ -0,0 +1,119 @@
+package server_test
+
+import (
+ "context"
+ "io/ioutil"
+ "net/http"
+ "os"
+ "testing"
+ "time"
+
+ "github.com/cpanato/github_actions_exporter/internal/server"
+ "github.com/go-kit/kit/log"
+ "github.com/google/go-github/v43/github"
+ "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
+)
+
+func Test_Server_MetricsRouteWithNoMetrics(t *testing.T) {
+ logger := log.NewLogfmtLogger(log.NewSyncWriter(os.Stderr))
+ srv := server.NewServer(logger, server.ServerOpts{
+ MetricsPath: "/metrics",
+ ListenAddress: ":8000",
+ WebhookPath: "/webhook",
+ GitHubToken: "webhook_token",
+ GitHubUser: "user",
+ GitHubOrg: "org",
+ GitHubAPIToken: "api_token",
+ })
+
+ t.Cleanup(func() {
+ err := srv.Shutdown(context.Background())
+ require.NoError(t, err)
+ })
+
+ go func() {
+ t.Log("start server")
+ err := srv.Serve(context.Background())
+ require.NoError(t, err)
+ t.Log("server shutdown")
+ }()
+
+ res, err := http.Get("http://localhost:8000/metrics")
+ require.NoError(t, err)
+ defer res.Body.Close()
+
+ assert.Equal(t, 200, res.StatusCode)
+
+ payload, err := ioutil.ReadAll(res.Body)
+ require.NoError(t, err)
+ assert.NotNil(t, payload)
+}
+
+func Test_Server_MetricsRouteAfterWorkflowJob(t *testing.T) {
+ logger := log.NewLogfmtLogger(log.NewSyncWriter(os.Stderr))
+ srv := server.NewServer(logger, server.ServerOpts{
+ MetricsPath: "/metrics",
+ ListenAddress: ":8000",
+ WebhookPath: "/webhook",
+ GitHubToken: webhookSecret,
+ GitHubUser: "user",
+ GitHubOrg: "org",
+ GitHubAPIToken: "api_token",
+ })
+
+ t.Cleanup(func() {
+ err := srv.Shutdown(context.Background())
+ require.NoError(t, err)
+ })
+
+ go func() {
+ t.Log("start server")
+ err := srv.Serve(context.Background())
+ require.NoError(t, err)
+ t.Log("server shutdown")
+ }()
+
+ repo := "some-repo"
+ org := "someone"
+ expectedDuration := 10.0
+ jobStartedAt := time.Unix(1650308740, 0)
+ stepStartedAt := jobStartedAt.Add(time.Duration(expectedDuration) * time.Second)
+ runnerGroupName := "runner-group"
+
+ event := github.WorkflowJobEvent{
+ Action: github.String("in_progress"),
+ Repo: &github.Repository{
+ Name: &repo,
+ Owner: &github.User{
+ Login: &org,
+ },
+ },
+ WorkflowJob: &github.WorkflowJob{
+ StartedAt: &github.Timestamp{Time: jobStartedAt},
+ Steps: []*github.TaskStep{
+ {
+ StartedAt: &github.Timestamp{Time: stepStartedAt},
+ },
+ },
+ RunnerGroupName: &runnerGroupName,
+ },
+ }
+ req := testWebhookRequest(t, "http://localhost:8000/webhook", "workflow_job", event)
+ res, err := http.DefaultClient.Do(req)
+ require.NoError(t, err)
+ defer res.Body.Close()
+ require.Equal(t, 202, res.StatusCode)
+
+ time.Sleep(2 * time.Second)
+
+ metricsRes, err := http.Get("http://localhost:8000/metrics")
+ require.NoError(t, err)
+ defer metricsRes.Body.Close()
+
+ assert.Equal(t, 200, metricsRes.StatusCode)
+
+ payload, err := ioutil.ReadAll(metricsRes.Body)
+ require.NoError(t, err)
+ assert.Contains(t, string(payload), `workflow_job_duration_seconds_bucket{org="someone",repo="some-repo",runner_group="runner-group",state="queued",le="10.541350399999995"} 1`)
+}
diff --git a/main.go b/main.go
index cb74d32..e7d4294 100644
--- a/main.go
+++ b/main.go
@@ -4,22 +4,16 @@ import (
"context"
"errors"
"fmt"
- "net"
- "net/http"
"os"
"os/signal"
- "strings"
"syscall"
- "github.com/go-kit/kit/log"
+ "github.com/cpanato/github_actions_exporter/internal/server"
"github.com/go-kit/kit/log/level"
- "github.com/google/go-github/v33/github"
"github.com/prometheus/client_golang/prometheus"
- "github.com/prometheus/client_golang/prometheus/promhttp"
"github.com/prometheus/common/promlog"
"github.com/prometheus/common/promlog/flag"
"github.com/prometheus/common/version"
- "golang.org/x/oauth2"
"gopkg.in/alecthomas/kingpin.v2"
)
@@ -33,12 +27,6 @@ var (
gitHubUser = kingpin.Flag("gh.github-user", "GitHub User.").Default("").String()
)
-// GHActionExporter struct to hold some information
-type GHActionExporter struct {
- GHClient *github.Client
- Logger log.Logger
-}
-
func init() {
prometheus.MustRegister(version.NewCollector("ghactions_exporter"))
}
@@ -59,85 +47,34 @@ func main() {
os.Exit(1)
}
- gh := NewGHActionExporter(logger)
-
- srv := http.Server{}
-
signalChan := make(chan os.Signal, 1)
signal.Notify(signalChan, os.Interrupt, syscall.SIGTERM)
+
+ srv := server.NewServer(logger, server.ServerOpts{
+ WebhookPath: *ghWebHookPath,
+ ListenAddress: *listenAddress,
+ MetricsPath: *metricsPath,
+ GitHubToken: *gitHubToken,
+ GitHubAPIToken: *gitHubAPIToken,
+ GitHubUser: *gitHubUser,
+ GitHubOrg: *gitHubOrg,
+ })
go func() {
- level.Info(logger).Log("msg", fmt.Sprintf("Signal received: %v. Exiting...", <-signalChan))
- err := srv.Close()
+ err := srv.Serve(context.Background())
if err != nil {
- level.Error(logger).Log("msg", "Error occurred while closing the server", "err", err)
+ level.Error(logger).Log("msg", "Server closed", "err", err)
+ } else {
+ level.Info(logger).Log("msg", "Server closed")
}
- os.Exit(0)
}()
- http.Handle(*metricsPath, promhttp.Handler())
- http.HandleFunc(*ghWebHookPath, gh.handleGHWebHook)
- http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
- w.Write([]byte(`
-GitHub Actions Exporter
-
-GitHub Actions Exporter
- ` + version.Print("ghactions_exporter") + `
-Metrics
-
-
-`))
- })
-
- listener, err := getListener(*listenAddress, logger)
+ level.Info(logger).Log("msg", fmt.Sprintf("Signal received: %v. Exiting...", <-signalChan))
+ err := srv.Shutdown(context.Background())
if err != nil {
- level.Error(logger).Log("msg", "Could not create listener", "err", err)
- os.Exit(1)
- }
-
- level.Info(logger).Log("msg", "GitHub Actions Prometheus Exporter has successfully started")
- if err := srv.Serve(listener); err != nil {
- level.Error(logger).Log("msg", "Error starting HTTP server", "err", err)
+ level.Error(logger).Log("msg", "Error occurred while closing the server", "err", err)
os.Exit(1)
}
-}
-
-func getListener(listenAddress string, logger log.Logger) (net.Listener, error) {
- var listener net.Listener
- var err error
-
- if strings.HasPrefix(listenAddress, "unix:") {
- path, _, pathError := parseUnixSocketAddress(listenAddress)
- if pathError != nil {
- return listener, fmt.Errorf("parsing unix domain socket listen address %s failed: %v", listenAddress, pathError)
- }
- listener, err = net.ListenUnix("unix", &net.UnixAddr{Name: path, Net: "unix"})
- } else {
- listener, err = net.Listen("tcp", listenAddress)
- }
-
- if err != nil {
- return listener, err
- }
-
- level.Info(logger).Log("msg", fmt.Sprintf("Listening on %s", listenAddress))
- return listener, nil
-}
-
-func parseUnixSocketAddress(address string) (string, string, error) {
- addressParts := strings.Split(address, ":")
- addressPartsLength := len(addressParts)
-
- if addressPartsLength > 3 || addressPartsLength < 1 {
- return "", "", fmt.Errorf("address for unix domain socket has wrong format")
- }
-
- unixSocketPath := addressParts[1]
- requestPath := ""
- if addressPartsLength == 3 {
- requestPath = addressParts[2]
- }
-
- return unixSocketPath, requestPath, nil
+ os.Exit(0)
}
func validateFlags(apiToken, token, org, user string) error {
@@ -156,17 +93,3 @@ func validateFlags(apiToken, token, org, user string) error {
return nil
}
-
-func NewGHActionExporter(logger log.Logger) *GHActionExporter {
- ctx := context.Background()
- ts := oauth2.StaticTokenSource(
- &oauth2.Token{AccessToken: *gitHubAPIToken},
- )
- tc := oauth2.NewClient(ctx, ts)
- client := github.NewClient(tc)
-
- return &GHActionExporter{
- GHClient: client,
- Logger: logger,
- }
-}
diff --git a/model/check_run_event.go b/model/check_run_event.go
index d9c1dac..ccead6f 100644
--- a/model/check_run_event.go
+++ b/model/check_run_event.go
@@ -4,7 +4,7 @@ import (
"encoding/json"
"io"
- "github.com/google/go-github/v33/github"
+ "github.com/google/go-github/v43/github"
)
// CheckRunEventFromJSON decodes the incomming message to a github.CheckRunEvent
diff --git a/model/ping_event.go b/model/ping_event.go
index 0d1958d..4868d34 100644
--- a/model/ping_event.go
+++ b/model/ping_event.go
@@ -4,7 +4,7 @@ import (
"encoding/json"
"io"
- "github.com/google/go-github/v33/github"
+ "github.com/google/go-github/v43/github"
)
// PingEventFromJSON decodes the incomming message to a github.PingEvent
diff --git a/model/workflow_job_event.go b/model/workflow_job_event.go
new file mode 100644
index 0000000..6e90c48
--- /dev/null
+++ b/model/workflow_job_event.go
@@ -0,0 +1,17 @@
+package model
+
+import (
+ "encoding/json"
+ "io"
+
+ "github.com/google/go-github/v43/github"
+)
+
+func WorkflowJobEventFromJSON(data io.Reader) *github.WorkflowJobEvent {
+ decoder := json.NewDecoder(data)
+ var event github.WorkflowJobEvent
+ if err := decoder.Decode(&event); err != nil {
+ return nil
+ }
+ return &event
+}
diff --git a/vendor/github.com/davecgh/go-spew/LICENSE b/vendor/github.com/davecgh/go-spew/LICENSE
new file mode 100644
index 0000000..bc52e96
--- /dev/null
+++ b/vendor/github.com/davecgh/go-spew/LICENSE
@@ -0,0 +1,15 @@
+ISC License
+
+Copyright (c) 2012-2016 Dave Collins
+
+Permission to use, copy, modify, and/or distribute this software for any
+purpose with or without fee is hereby granted, provided that the above
+copyright notice and this permission notice appear in all copies.
+
+THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
diff --git a/vendor/github.com/davecgh/go-spew/spew/bypass.go b/vendor/github.com/davecgh/go-spew/spew/bypass.go
new file mode 100644
index 0000000..7929947
--- /dev/null
+++ b/vendor/github.com/davecgh/go-spew/spew/bypass.go
@@ -0,0 +1,145 @@
+// Copyright (c) 2015-2016 Dave Collins
+//
+// Permission to use, copy, modify, and distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+// NOTE: Due to the following build constraints, this file will only be compiled
+// when the code is not running on Google App Engine, compiled by GopherJS, and
+// "-tags safe" is not added to the go build command line. The "disableunsafe"
+// tag is deprecated and thus should not be used.
+// Go versions prior to 1.4 are disabled because they use a different layout
+// for interfaces which make the implementation of unsafeReflectValue more complex.
+// +build !js,!appengine,!safe,!disableunsafe,go1.4
+
+package spew
+
+import (
+ "reflect"
+ "unsafe"
+)
+
+const (
+ // UnsafeDisabled is a build-time constant which specifies whether or
+ // not access to the unsafe package is available.
+ UnsafeDisabled = false
+
+ // ptrSize is the size of a pointer on the current arch.
+ ptrSize = unsafe.Sizeof((*byte)(nil))
+)
+
+type flag uintptr
+
+var (
+ // flagRO indicates whether the value field of a reflect.Value
+ // is read-only.
+ flagRO flag
+
+ // flagAddr indicates whether the address of the reflect.Value's
+ // value may be taken.
+ flagAddr flag
+)
+
+// flagKindMask holds the bits that make up the kind
+// part of the flags field. In all the supported versions,
+// it is in the lower 5 bits.
+const flagKindMask = flag(0x1f)
+
+// Different versions of Go have used different
+// bit layouts for the flags type. This table
+// records the known combinations.
+var okFlags = []struct {
+ ro, addr flag
+}{{
+ // From Go 1.4 to 1.5
+ ro: 1 << 5,
+ addr: 1 << 7,
+}, {
+ // Up to Go tip.
+ ro: 1<<5 | 1<<6,
+ addr: 1 << 8,
+}}
+
+var flagValOffset = func() uintptr {
+ field, ok := reflect.TypeOf(reflect.Value{}).FieldByName("flag")
+ if !ok {
+ panic("reflect.Value has no flag field")
+ }
+ return field.Offset
+}()
+
+// flagField returns a pointer to the flag field of a reflect.Value.
+func flagField(v *reflect.Value) *flag {
+ return (*flag)(unsafe.Pointer(uintptr(unsafe.Pointer(v)) + flagValOffset))
+}
+
+// unsafeReflectValue converts the passed reflect.Value into a one that bypasses
+// the typical safety restrictions preventing access to unaddressable and
+// unexported data. It works by digging the raw pointer to the underlying
+// value out of the protected value and generating a new unprotected (unsafe)
+// reflect.Value to it.
+//
+// This allows us to check for implementations of the Stringer and error
+// interfaces to be used for pretty printing ordinarily unaddressable and
+// inaccessible values such as unexported struct fields.
+func unsafeReflectValue(v reflect.Value) reflect.Value {
+ if !v.IsValid() || (v.CanInterface() && v.CanAddr()) {
+ return v
+ }
+ flagFieldPtr := flagField(&v)
+ *flagFieldPtr &^= flagRO
+ *flagFieldPtr |= flagAddr
+ return v
+}
+
+// Sanity checks against future reflect package changes
+// to the type or semantics of the Value.flag field.
+func init() {
+ field, ok := reflect.TypeOf(reflect.Value{}).FieldByName("flag")
+ if !ok {
+ panic("reflect.Value has no flag field")
+ }
+ if field.Type.Kind() != reflect.TypeOf(flag(0)).Kind() {
+ panic("reflect.Value flag field has changed kind")
+ }
+ type t0 int
+ var t struct {
+ A t0
+ // t0 will have flagEmbedRO set.
+ t0
+ // a will have flagStickyRO set
+ a t0
+ }
+ vA := reflect.ValueOf(t).FieldByName("A")
+ va := reflect.ValueOf(t).FieldByName("a")
+ vt0 := reflect.ValueOf(t).FieldByName("t0")
+
+ // Infer flagRO from the difference between the flags
+ // for the (otherwise identical) fields in t.
+ flagPublic := *flagField(&vA)
+ flagWithRO := *flagField(&va) | *flagField(&vt0)
+ flagRO = flagPublic ^ flagWithRO
+
+ // Infer flagAddr from the difference between a value
+ // taken from a pointer and not.
+ vPtrA := reflect.ValueOf(&t).Elem().FieldByName("A")
+ flagNoPtr := *flagField(&vA)
+ flagPtr := *flagField(&vPtrA)
+ flagAddr = flagNoPtr ^ flagPtr
+
+ // Check that the inferred flags tally with one of the known versions.
+ for _, f := range okFlags {
+ if flagRO == f.ro && flagAddr == f.addr {
+ return
+ }
+ }
+ panic("reflect.Value read-only flag has changed semantics")
+}
diff --git a/vendor/github.com/davecgh/go-spew/spew/bypasssafe.go b/vendor/github.com/davecgh/go-spew/spew/bypasssafe.go
new file mode 100644
index 0000000..205c28d
--- /dev/null
+++ b/vendor/github.com/davecgh/go-spew/spew/bypasssafe.go
@@ -0,0 +1,38 @@
+// Copyright (c) 2015-2016 Dave Collins
+//
+// Permission to use, copy, modify, and distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+// NOTE: Due to the following build constraints, this file will only be compiled
+// when the code is running on Google App Engine, compiled by GopherJS, or
+// "-tags safe" is added to the go build command line. The "disableunsafe"
+// tag is deprecated and thus should not be used.
+// +build js appengine safe disableunsafe !go1.4
+
+package spew
+
+import "reflect"
+
+const (
+ // UnsafeDisabled is a build-time constant which specifies whether or
+ // not access to the unsafe package is available.
+ UnsafeDisabled = true
+)
+
+// unsafeReflectValue typically converts the passed reflect.Value into a one
+// that bypasses the typical safety restrictions preventing access to
+// unaddressable and unexported data. However, doing this relies on access to
+// the unsafe package. This is a stub version which simply returns the passed
+// reflect.Value when the unsafe package is not available.
+func unsafeReflectValue(v reflect.Value) reflect.Value {
+ return v
+}
diff --git a/vendor/github.com/davecgh/go-spew/spew/common.go b/vendor/github.com/davecgh/go-spew/spew/common.go
new file mode 100644
index 0000000..1be8ce9
--- /dev/null
+++ b/vendor/github.com/davecgh/go-spew/spew/common.go
@@ -0,0 +1,341 @@
+/*
+ * Copyright (c) 2013-2016 Dave Collins
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+package spew
+
+import (
+ "bytes"
+ "fmt"
+ "io"
+ "reflect"
+ "sort"
+ "strconv"
+)
+
+// Some constants in the form of bytes to avoid string overhead. This mirrors
+// the technique used in the fmt package.
+var (
+ panicBytes = []byte("(PANIC=")
+ plusBytes = []byte("+")
+ iBytes = []byte("i")
+ trueBytes = []byte("true")
+ falseBytes = []byte("false")
+ interfaceBytes = []byte("(interface {})")
+ commaNewlineBytes = []byte(",\n")
+ newlineBytes = []byte("\n")
+ openBraceBytes = []byte("{")
+ openBraceNewlineBytes = []byte("{\n")
+ closeBraceBytes = []byte("}")
+ asteriskBytes = []byte("*")
+ colonBytes = []byte(":")
+ colonSpaceBytes = []byte(": ")
+ openParenBytes = []byte("(")
+ closeParenBytes = []byte(")")
+ spaceBytes = []byte(" ")
+ pointerChainBytes = []byte("->")
+ nilAngleBytes = []byte("")
+ maxNewlineBytes = []byte("\n")
+ maxShortBytes = []byte("")
+ circularBytes = []byte("")
+ circularShortBytes = []byte("")
+ invalidAngleBytes = []byte("")
+ openBracketBytes = []byte("[")
+ closeBracketBytes = []byte("]")
+ percentBytes = []byte("%")
+ precisionBytes = []byte(".")
+ openAngleBytes = []byte("<")
+ closeAngleBytes = []byte(">")
+ openMapBytes = []byte("map[")
+ closeMapBytes = []byte("]")
+ lenEqualsBytes = []byte("len=")
+ capEqualsBytes = []byte("cap=")
+)
+
+// hexDigits is used to map a decimal value to a hex digit.
+var hexDigits = "0123456789abcdef"
+
+// catchPanic handles any panics that might occur during the handleMethods
+// calls.
+func catchPanic(w io.Writer, v reflect.Value) {
+ if err := recover(); err != nil {
+ w.Write(panicBytes)
+ fmt.Fprintf(w, "%v", err)
+ w.Write(closeParenBytes)
+ }
+}
+
+// handleMethods attempts to call the Error and String methods on the underlying
+// type the passed reflect.Value represents and outputes the result to Writer w.
+//
+// It handles panics in any called methods by catching and displaying the error
+// as the formatted value.
+func handleMethods(cs *ConfigState, w io.Writer, v reflect.Value) (handled bool) {
+ // We need an interface to check if the type implements the error or
+ // Stringer interface. However, the reflect package won't give us an
+ // interface on certain things like unexported struct fields in order
+ // to enforce visibility rules. We use unsafe, when it's available,
+ // to bypass these restrictions since this package does not mutate the
+ // values.
+ if !v.CanInterface() {
+ if UnsafeDisabled {
+ return false
+ }
+
+ v = unsafeReflectValue(v)
+ }
+
+ // Choose whether or not to do error and Stringer interface lookups against
+ // the base type or a pointer to the base type depending on settings.
+ // Technically calling one of these methods with a pointer receiver can
+ // mutate the value, however, types which choose to satisify an error or
+ // Stringer interface with a pointer receiver should not be mutating their
+ // state inside these interface methods.
+ if !cs.DisablePointerMethods && !UnsafeDisabled && !v.CanAddr() {
+ v = unsafeReflectValue(v)
+ }
+ if v.CanAddr() {
+ v = v.Addr()
+ }
+
+ // Is it an error or Stringer?
+ switch iface := v.Interface().(type) {
+ case error:
+ defer catchPanic(w, v)
+ if cs.ContinueOnMethod {
+ w.Write(openParenBytes)
+ w.Write([]byte(iface.Error()))
+ w.Write(closeParenBytes)
+ w.Write(spaceBytes)
+ return false
+ }
+
+ w.Write([]byte(iface.Error()))
+ return true
+
+ case fmt.Stringer:
+ defer catchPanic(w, v)
+ if cs.ContinueOnMethod {
+ w.Write(openParenBytes)
+ w.Write([]byte(iface.String()))
+ w.Write(closeParenBytes)
+ w.Write(spaceBytes)
+ return false
+ }
+ w.Write([]byte(iface.String()))
+ return true
+ }
+ return false
+}
+
+// printBool outputs a boolean value as true or false to Writer w.
+func printBool(w io.Writer, val bool) {
+ if val {
+ w.Write(trueBytes)
+ } else {
+ w.Write(falseBytes)
+ }
+}
+
+// printInt outputs a signed integer value to Writer w.
+func printInt(w io.Writer, val int64, base int) {
+ w.Write([]byte(strconv.FormatInt(val, base)))
+}
+
+// printUint outputs an unsigned integer value to Writer w.
+func printUint(w io.Writer, val uint64, base int) {
+ w.Write([]byte(strconv.FormatUint(val, base)))
+}
+
+// printFloat outputs a floating point value using the specified precision,
+// which is expected to be 32 or 64bit, to Writer w.
+func printFloat(w io.Writer, val float64, precision int) {
+ w.Write([]byte(strconv.FormatFloat(val, 'g', -1, precision)))
+}
+
+// printComplex outputs a complex value using the specified float precision
+// for the real and imaginary parts to Writer w.
+func printComplex(w io.Writer, c complex128, floatPrecision int) {
+ r := real(c)
+ w.Write(openParenBytes)
+ w.Write([]byte(strconv.FormatFloat(r, 'g', -1, floatPrecision)))
+ i := imag(c)
+ if i >= 0 {
+ w.Write(plusBytes)
+ }
+ w.Write([]byte(strconv.FormatFloat(i, 'g', -1, floatPrecision)))
+ w.Write(iBytes)
+ w.Write(closeParenBytes)
+}
+
+// printHexPtr outputs a uintptr formatted as hexadecimal with a leading '0x'
+// prefix to Writer w.
+func printHexPtr(w io.Writer, p uintptr) {
+ // Null pointer.
+ num := uint64(p)
+ if num == 0 {
+ w.Write(nilAngleBytes)
+ return
+ }
+
+ // Max uint64 is 16 bytes in hex + 2 bytes for '0x' prefix
+ buf := make([]byte, 18)
+
+ // It's simpler to construct the hex string right to left.
+ base := uint64(16)
+ i := len(buf) - 1
+ for num >= base {
+ buf[i] = hexDigits[num%base]
+ num /= base
+ i--
+ }
+ buf[i] = hexDigits[num]
+
+ // Add '0x' prefix.
+ i--
+ buf[i] = 'x'
+ i--
+ buf[i] = '0'
+
+ // Strip unused leading bytes.
+ buf = buf[i:]
+ w.Write(buf)
+}
+
+// valuesSorter implements sort.Interface to allow a slice of reflect.Value
+// elements to be sorted.
+type valuesSorter struct {
+ values []reflect.Value
+ strings []string // either nil or same len and values
+ cs *ConfigState
+}
+
+// newValuesSorter initializes a valuesSorter instance, which holds a set of
+// surrogate keys on which the data should be sorted. It uses flags in
+// ConfigState to decide if and how to populate those surrogate keys.
+func newValuesSorter(values []reflect.Value, cs *ConfigState) sort.Interface {
+ vs := &valuesSorter{values: values, cs: cs}
+ if canSortSimply(vs.values[0].Kind()) {
+ return vs
+ }
+ if !cs.DisableMethods {
+ vs.strings = make([]string, len(values))
+ for i := range vs.values {
+ b := bytes.Buffer{}
+ if !handleMethods(cs, &b, vs.values[i]) {
+ vs.strings = nil
+ break
+ }
+ vs.strings[i] = b.String()
+ }
+ }
+ if vs.strings == nil && cs.SpewKeys {
+ vs.strings = make([]string, len(values))
+ for i := range vs.values {
+ vs.strings[i] = Sprintf("%#v", vs.values[i].Interface())
+ }
+ }
+ return vs
+}
+
+// canSortSimply tests whether a reflect.Kind is a primitive that can be sorted
+// directly, or whether it should be considered for sorting by surrogate keys
+// (if the ConfigState allows it).
+func canSortSimply(kind reflect.Kind) bool {
+ // This switch parallels valueSortLess, except for the default case.
+ switch kind {
+ case reflect.Bool:
+ return true
+ case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int:
+ return true
+ case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint:
+ return true
+ case reflect.Float32, reflect.Float64:
+ return true
+ case reflect.String:
+ return true
+ case reflect.Uintptr:
+ return true
+ case reflect.Array:
+ return true
+ }
+ return false
+}
+
+// Len returns the number of values in the slice. It is part of the
+// sort.Interface implementation.
+func (s *valuesSorter) Len() int {
+ return len(s.values)
+}
+
+// Swap swaps the values at the passed indices. It is part of the
+// sort.Interface implementation.
+func (s *valuesSorter) Swap(i, j int) {
+ s.values[i], s.values[j] = s.values[j], s.values[i]
+ if s.strings != nil {
+ s.strings[i], s.strings[j] = s.strings[j], s.strings[i]
+ }
+}
+
+// valueSortLess returns whether the first value should sort before the second
+// value. It is used by valueSorter.Less as part of the sort.Interface
+// implementation.
+func valueSortLess(a, b reflect.Value) bool {
+ switch a.Kind() {
+ case reflect.Bool:
+ return !a.Bool() && b.Bool()
+ case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int:
+ return a.Int() < b.Int()
+ case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint:
+ return a.Uint() < b.Uint()
+ case reflect.Float32, reflect.Float64:
+ return a.Float() < b.Float()
+ case reflect.String:
+ return a.String() < b.String()
+ case reflect.Uintptr:
+ return a.Uint() < b.Uint()
+ case reflect.Array:
+ // Compare the contents of both arrays.
+ l := a.Len()
+ for i := 0; i < l; i++ {
+ av := a.Index(i)
+ bv := b.Index(i)
+ if av.Interface() == bv.Interface() {
+ continue
+ }
+ return valueSortLess(av, bv)
+ }
+ }
+ return a.String() < b.String()
+}
+
+// Less returns whether the value at index i should sort before the
+// value at index j. It is part of the sort.Interface implementation.
+func (s *valuesSorter) Less(i, j int) bool {
+ if s.strings == nil {
+ return valueSortLess(s.values[i], s.values[j])
+ }
+ return s.strings[i] < s.strings[j]
+}
+
+// sortValues is a sort function that handles both native types and any type that
+// can be converted to error or Stringer. Other inputs are sorted according to
+// their Value.String() value to ensure display stability.
+func sortValues(values []reflect.Value, cs *ConfigState) {
+ if len(values) == 0 {
+ return
+ }
+ sort.Sort(newValuesSorter(values, cs))
+}
diff --git a/vendor/github.com/davecgh/go-spew/spew/config.go b/vendor/github.com/davecgh/go-spew/spew/config.go
new file mode 100644
index 0000000..2e3d22f
--- /dev/null
+++ b/vendor/github.com/davecgh/go-spew/spew/config.go
@@ -0,0 +1,306 @@
+/*
+ * Copyright (c) 2013-2016 Dave Collins
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+package spew
+
+import (
+ "bytes"
+ "fmt"
+ "io"
+ "os"
+)
+
+// ConfigState houses the configuration options used by spew to format and
+// display values. There is a global instance, Config, that is used to control
+// all top-level Formatter and Dump functionality. Each ConfigState instance
+// provides methods equivalent to the top-level functions.
+//
+// The zero value for ConfigState provides no indentation. You would typically
+// want to set it to a space or a tab.
+//
+// Alternatively, you can use NewDefaultConfig to get a ConfigState instance
+// with default settings. See the documentation of NewDefaultConfig for default
+// values.
+type ConfigState struct {
+ // Indent specifies the string to use for each indentation level. The
+ // global config instance that all top-level functions use set this to a
+ // single space by default. If you would like more indentation, you might
+ // set this to a tab with "\t" or perhaps two spaces with " ".
+ Indent string
+
+ // MaxDepth controls the maximum number of levels to descend into nested
+ // data structures. The default, 0, means there is no limit.
+ //
+ // NOTE: Circular data structures are properly detected, so it is not
+ // necessary to set this value unless you specifically want to limit deeply
+ // nested data structures.
+ MaxDepth int
+
+ // DisableMethods specifies whether or not error and Stringer interfaces are
+ // invoked for types that implement them.
+ DisableMethods bool
+
+ // DisablePointerMethods specifies whether or not to check for and invoke
+ // error and Stringer interfaces on types which only accept a pointer
+ // receiver when the current type is not a pointer.
+ //
+ // NOTE: This might be an unsafe action since calling one of these methods
+ // with a pointer receiver could technically mutate the value, however,
+ // in practice, types which choose to satisify an error or Stringer
+ // interface with a pointer receiver should not be mutating their state
+ // inside these interface methods. As a result, this option relies on
+ // access to the unsafe package, so it will not have any effect when
+ // running in environments without access to the unsafe package such as
+ // Google App Engine or with the "safe" build tag specified.
+ DisablePointerMethods bool
+
+ // DisablePointerAddresses specifies whether to disable the printing of
+ // pointer addresses. This is useful when diffing data structures in tests.
+ DisablePointerAddresses bool
+
+ // DisableCapacities specifies whether to disable the printing of capacities
+ // for arrays, slices, maps and channels. This is useful when diffing
+ // data structures in tests.
+ DisableCapacities bool
+
+ // ContinueOnMethod specifies whether or not recursion should continue once
+ // a custom error or Stringer interface is invoked. The default, false,
+ // means it will print the results of invoking the custom error or Stringer
+ // interface and return immediately instead of continuing to recurse into
+ // the internals of the data type.
+ //
+ // NOTE: This flag does not have any effect if method invocation is disabled
+ // via the DisableMethods or DisablePointerMethods options.
+ ContinueOnMethod bool
+
+ // SortKeys specifies map keys should be sorted before being printed. Use
+ // this to have a more deterministic, diffable output. Note that only
+ // native types (bool, int, uint, floats, uintptr and string) and types
+ // that support the error or Stringer interfaces (if methods are
+ // enabled) are supported, with other types sorted according to the
+ // reflect.Value.String() output which guarantees display stability.
+ SortKeys bool
+
+ // SpewKeys specifies that, as a last resort attempt, map keys should
+ // be spewed to strings and sorted by those strings. This is only
+ // considered if SortKeys is true.
+ SpewKeys bool
+}
+
+// Config is the active configuration of the top-level functions.
+// The configuration can be changed by modifying the contents of spew.Config.
+var Config = ConfigState{Indent: " "}
+
+// Errorf is a wrapper for fmt.Errorf that treats each argument as if it were
+// passed with a Formatter interface returned by c.NewFormatter. It returns
+// the formatted string as a value that satisfies error. See NewFormatter
+// for formatting details.
+//
+// This function is shorthand for the following syntax:
+//
+// fmt.Errorf(format, c.NewFormatter(a), c.NewFormatter(b))
+func (c *ConfigState) Errorf(format string, a ...interface{}) (err error) {
+ return fmt.Errorf(format, c.convertArgs(a)...)
+}
+
+// Fprint is a wrapper for fmt.Fprint that treats each argument as if it were
+// passed with a Formatter interface returned by c.NewFormatter. It returns
+// the number of bytes written and any write error encountered. See
+// NewFormatter for formatting details.
+//
+// This function is shorthand for the following syntax:
+//
+// fmt.Fprint(w, c.NewFormatter(a), c.NewFormatter(b))
+func (c *ConfigState) Fprint(w io.Writer, a ...interface{}) (n int, err error) {
+ return fmt.Fprint(w, c.convertArgs(a)...)
+}
+
+// Fprintf is a wrapper for fmt.Fprintf that treats each argument as if it were
+// passed with a Formatter interface returned by c.NewFormatter. It returns
+// the number of bytes written and any write error encountered. See
+// NewFormatter for formatting details.
+//
+// This function is shorthand for the following syntax:
+//
+// fmt.Fprintf(w, format, c.NewFormatter(a), c.NewFormatter(b))
+func (c *ConfigState) Fprintf(w io.Writer, format string, a ...interface{}) (n int, err error) {
+ return fmt.Fprintf(w, format, c.convertArgs(a)...)
+}
+
+// Fprintln is a wrapper for fmt.Fprintln that treats each argument as if it
+// passed with a Formatter interface returned by c.NewFormatter. See
+// NewFormatter for formatting details.
+//
+// This function is shorthand for the following syntax:
+//
+// fmt.Fprintln(w, c.NewFormatter(a), c.NewFormatter(b))
+func (c *ConfigState) Fprintln(w io.Writer, a ...interface{}) (n int, err error) {
+ return fmt.Fprintln(w, c.convertArgs(a)...)
+}
+
+// Print is a wrapper for fmt.Print that treats each argument as if it were
+// passed with a Formatter interface returned by c.NewFormatter. It returns
+// the number of bytes written and any write error encountered. See
+// NewFormatter for formatting details.
+//
+// This function is shorthand for the following syntax:
+//
+// fmt.Print(c.NewFormatter(a), c.NewFormatter(b))
+func (c *ConfigState) Print(a ...interface{}) (n int, err error) {
+ return fmt.Print(c.convertArgs(a)...)
+}
+
+// Printf is a wrapper for fmt.Printf that treats each argument as if it were
+// passed with a Formatter interface returned by c.NewFormatter. It returns
+// the number of bytes written and any write error encountered. See
+// NewFormatter for formatting details.
+//
+// This function is shorthand for the following syntax:
+//
+// fmt.Printf(format, c.NewFormatter(a), c.NewFormatter(b))
+func (c *ConfigState) Printf(format string, a ...interface{}) (n int, err error) {
+ return fmt.Printf(format, c.convertArgs(a)...)
+}
+
+// Println is a wrapper for fmt.Println that treats each argument as if it were
+// passed with a Formatter interface returned by c.NewFormatter. It returns
+// the number of bytes written and any write error encountered. See
+// NewFormatter for formatting details.
+//
+// This function is shorthand for the following syntax:
+//
+// fmt.Println(c.NewFormatter(a), c.NewFormatter(b))
+func (c *ConfigState) Println(a ...interface{}) (n int, err error) {
+ return fmt.Println(c.convertArgs(a)...)
+}
+
+// Sprint is a wrapper for fmt.Sprint that treats each argument as if it were
+// passed with a Formatter interface returned by c.NewFormatter. It returns
+// the resulting string. See NewFormatter for formatting details.
+//
+// This function is shorthand for the following syntax:
+//
+// fmt.Sprint(c.NewFormatter(a), c.NewFormatter(b))
+func (c *ConfigState) Sprint(a ...interface{}) string {
+ return fmt.Sprint(c.convertArgs(a)...)
+}
+
+// Sprintf is a wrapper for fmt.Sprintf that treats each argument as if it were
+// passed with a Formatter interface returned by c.NewFormatter. It returns
+// the resulting string. See NewFormatter for formatting details.
+//
+// This function is shorthand for the following syntax:
+//
+// fmt.Sprintf(format, c.NewFormatter(a), c.NewFormatter(b))
+func (c *ConfigState) Sprintf(format string, a ...interface{}) string {
+ return fmt.Sprintf(format, c.convertArgs(a)...)
+}
+
+// Sprintln is a wrapper for fmt.Sprintln that treats each argument as if it
+// were passed with a Formatter interface returned by c.NewFormatter. It
+// returns the resulting string. See NewFormatter for formatting details.
+//
+// This function is shorthand for the following syntax:
+//
+// fmt.Sprintln(c.NewFormatter(a), c.NewFormatter(b))
+func (c *ConfigState) Sprintln(a ...interface{}) string {
+ return fmt.Sprintln(c.convertArgs(a)...)
+}
+
+/*
+NewFormatter returns a custom formatter that satisfies the fmt.Formatter
+interface. As a result, it integrates cleanly with standard fmt package
+printing functions. The formatter is useful for inline printing of smaller data
+types similar to the standard %v format specifier.
+
+The custom formatter only responds to the %v (most compact), %+v (adds pointer
+addresses), %#v (adds types), and %#+v (adds types and pointer addresses) verb
+combinations. Any other verbs such as %x and %q will be sent to the the
+standard fmt package for formatting. In addition, the custom formatter ignores
+the width and precision arguments (however they will still work on the format
+specifiers not handled by the custom formatter).
+
+Typically this function shouldn't be called directly. It is much easier to make
+use of the custom formatter by calling one of the convenience functions such as
+c.Printf, c.Println, or c.Printf.
+*/
+func (c *ConfigState) NewFormatter(v interface{}) fmt.Formatter {
+ return newFormatter(c, v)
+}
+
+// Fdump formats and displays the passed arguments to io.Writer w. It formats
+// exactly the same as Dump.
+func (c *ConfigState) Fdump(w io.Writer, a ...interface{}) {
+ fdump(c, w, a...)
+}
+
+/*
+Dump displays the passed parameters to standard out with newlines, customizable
+indentation, and additional debug information such as complete types and all
+pointer addresses used to indirect to the final value. It provides the
+following features over the built-in printing facilities provided by the fmt
+package:
+
+ * Pointers are dereferenced and followed
+ * Circular data structures are detected and handled properly
+ * Custom Stringer/error interfaces are optionally invoked, including
+ on unexported types
+ * Custom types which only implement the Stringer/error interfaces via
+ a pointer receiver are optionally invoked when passing non-pointer
+ variables
+ * Byte arrays and slices are dumped like the hexdump -C command which
+ includes offsets, byte values in hex, and ASCII output
+
+The configuration options are controlled by modifying the public members
+of c. See ConfigState for options documentation.
+
+See Fdump if you would prefer dumping to an arbitrary io.Writer or Sdump to
+get the formatted result as a string.
+*/
+func (c *ConfigState) Dump(a ...interface{}) {
+ fdump(c, os.Stdout, a...)
+}
+
+// Sdump returns a string with the passed arguments formatted exactly the same
+// as Dump.
+func (c *ConfigState) Sdump(a ...interface{}) string {
+ var buf bytes.Buffer
+ fdump(c, &buf, a...)
+ return buf.String()
+}
+
+// convertArgs accepts a slice of arguments and returns a slice of the same
+// length with each argument converted to a spew Formatter interface using
+// the ConfigState associated with s.
+func (c *ConfigState) convertArgs(args []interface{}) (formatters []interface{}) {
+ formatters = make([]interface{}, len(args))
+ for index, arg := range args {
+ formatters[index] = newFormatter(c, arg)
+ }
+ return formatters
+}
+
+// NewDefaultConfig returns a ConfigState with the following default settings.
+//
+// Indent: " "
+// MaxDepth: 0
+// DisableMethods: false
+// DisablePointerMethods: false
+// ContinueOnMethod: false
+// SortKeys: false
+func NewDefaultConfig() *ConfigState {
+ return &ConfigState{Indent: " "}
+}
diff --git a/vendor/github.com/davecgh/go-spew/spew/doc.go b/vendor/github.com/davecgh/go-spew/spew/doc.go
new file mode 100644
index 0000000..aacaac6
--- /dev/null
+++ b/vendor/github.com/davecgh/go-spew/spew/doc.go
@@ -0,0 +1,211 @@
+/*
+ * Copyright (c) 2013-2016 Dave Collins
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/*
+Package spew implements a deep pretty printer for Go data structures to aid in
+debugging.
+
+A quick overview of the additional features spew provides over the built-in
+printing facilities for Go data types are as follows:
+
+ * Pointers are dereferenced and followed
+ * Circular data structures are detected and handled properly
+ * Custom Stringer/error interfaces are optionally invoked, including
+ on unexported types
+ * Custom types which only implement the Stringer/error interfaces via
+ a pointer receiver are optionally invoked when passing non-pointer
+ variables
+ * Byte arrays and slices are dumped like the hexdump -C command which
+ includes offsets, byte values in hex, and ASCII output (only when using
+ Dump style)
+
+There are two different approaches spew allows for dumping Go data structures:
+
+ * Dump style which prints with newlines, customizable indentation,
+ and additional debug information such as types and all pointer addresses
+ used to indirect to the final value
+ * A custom Formatter interface that integrates cleanly with the standard fmt
+ package and replaces %v, %+v, %#v, and %#+v to provide inline printing
+ similar to the default %v while providing the additional functionality
+ outlined above and passing unsupported format verbs such as %x and %q
+ along to fmt
+
+Quick Start
+
+This section demonstrates how to quickly get started with spew. See the
+sections below for further details on formatting and configuration options.
+
+To dump a variable with full newlines, indentation, type, and pointer
+information use Dump, Fdump, or Sdump:
+ spew.Dump(myVar1, myVar2, ...)
+ spew.Fdump(someWriter, myVar1, myVar2, ...)
+ str := spew.Sdump(myVar1, myVar2, ...)
+
+Alternatively, if you would prefer to use format strings with a compacted inline
+printing style, use the convenience wrappers Printf, Fprintf, etc with
+%v (most compact), %+v (adds pointer addresses), %#v (adds types), or
+%#+v (adds types and pointer addresses):
+ spew.Printf("myVar1: %v -- myVar2: %+v", myVar1, myVar2)
+ spew.Printf("myVar3: %#v -- myVar4: %#+v", myVar3, myVar4)
+ spew.Fprintf(someWriter, "myVar1: %v -- myVar2: %+v", myVar1, myVar2)
+ spew.Fprintf(someWriter, "myVar3: %#v -- myVar4: %#+v", myVar3, myVar4)
+
+Configuration Options
+
+Configuration of spew is handled by fields in the ConfigState type. For
+convenience, all of the top-level functions use a global state available
+via the spew.Config global.
+
+It is also possible to create a ConfigState instance that provides methods
+equivalent to the top-level functions. This allows concurrent configuration
+options. See the ConfigState documentation for more details.
+
+The following configuration options are available:
+ * Indent
+ String to use for each indentation level for Dump functions.
+ It is a single space by default. A popular alternative is "\t".
+
+ * MaxDepth
+ Maximum number of levels to descend into nested data structures.
+ There is no limit by default.
+
+ * DisableMethods
+ Disables invocation of error and Stringer interface methods.
+ Method invocation is enabled by default.
+
+ * DisablePointerMethods
+ Disables invocation of error and Stringer interface methods on types
+ which only accept pointer receivers from non-pointer variables.
+ Pointer method invocation is enabled by default.
+
+ * DisablePointerAddresses
+ DisablePointerAddresses specifies whether to disable the printing of
+ pointer addresses. This is useful when diffing data structures in tests.
+
+ * DisableCapacities
+ DisableCapacities specifies whether to disable the printing of
+ capacities for arrays, slices, maps and channels. This is useful when
+ diffing data structures in tests.
+
+ * ContinueOnMethod
+ Enables recursion into types after invoking error and Stringer interface
+ methods. Recursion after method invocation is disabled by default.
+
+ * SortKeys
+ Specifies map keys should be sorted before being printed. Use
+ this to have a more deterministic, diffable output. Note that
+ only native types (bool, int, uint, floats, uintptr and string)
+ and types which implement error or Stringer interfaces are
+ supported with other types sorted according to the
+ reflect.Value.String() output which guarantees display
+ stability. Natural map order is used by default.
+
+ * SpewKeys
+ Specifies that, as a last resort attempt, map keys should be
+ spewed to strings and sorted by those strings. This is only
+ considered if SortKeys is true.
+
+Dump Usage
+
+Simply call spew.Dump with a list of variables you want to dump:
+
+ spew.Dump(myVar1, myVar2, ...)
+
+You may also call spew.Fdump if you would prefer to output to an arbitrary
+io.Writer. For example, to dump to standard error:
+
+ spew.Fdump(os.Stderr, myVar1, myVar2, ...)
+
+A third option is to call spew.Sdump to get the formatted output as a string:
+
+ str := spew.Sdump(myVar1, myVar2, ...)
+
+Sample Dump Output
+
+See the Dump example for details on the setup of the types and variables being
+shown here.
+
+ (main.Foo) {
+ unexportedField: (*main.Bar)(0xf84002e210)({
+ flag: (main.Flag) flagTwo,
+ data: (uintptr)
+ }),
+ ExportedField: (map[interface {}]interface {}) (len=1) {
+ (string) (len=3) "one": (bool) true
+ }
+ }
+
+Byte (and uint8) arrays and slices are displayed uniquely like the hexdump -C
+command as shown.
+ ([]uint8) (len=32 cap=32) {
+ 00000000 11 12 13 14 15 16 17 18 19 1a 1b 1c 1d 1e 1f 20 |............... |
+ 00000010 21 22 23 24 25 26 27 28 29 2a 2b 2c 2d 2e 2f 30 |!"#$%&'()*+,-./0|
+ 00000020 31 32 |12|
+ }
+
+Custom Formatter
+
+Spew provides a custom formatter that implements the fmt.Formatter interface
+so that it integrates cleanly with standard fmt package printing functions. The
+formatter is useful for inline printing of smaller data types similar to the
+standard %v format specifier.
+
+The custom formatter only responds to the %v (most compact), %+v (adds pointer
+addresses), %#v (adds types), or %#+v (adds types and pointer addresses) verb
+combinations. Any other verbs such as %x and %q will be sent to the the
+standard fmt package for formatting. In addition, the custom formatter ignores
+the width and precision arguments (however they will still work on the format
+specifiers not handled by the custom formatter).
+
+Custom Formatter Usage
+
+The simplest way to make use of the spew custom formatter is to call one of the
+convenience functions such as spew.Printf, spew.Println, or spew.Printf. The
+functions have syntax you are most likely already familiar with:
+
+ spew.Printf("myVar1: %v -- myVar2: %+v", myVar1, myVar2)
+ spew.Printf("myVar3: %#v -- myVar4: %#+v", myVar3, myVar4)
+ spew.Println(myVar, myVar2)
+ spew.Fprintf(os.Stderr, "myVar1: %v -- myVar2: %+v", myVar1, myVar2)
+ spew.Fprintf(os.Stderr, "myVar3: %#v -- myVar4: %#+v", myVar3, myVar4)
+
+See the Index for the full list convenience functions.
+
+Sample Formatter Output
+
+Double pointer to a uint8:
+ %v: <**>5
+ %+v: <**>(0xf8400420d0->0xf8400420c8)5
+ %#v: (**uint8)5
+ %#+v: (**uint8)(0xf8400420d0->0xf8400420c8)5
+
+Pointer to circular struct with a uint8 field and a pointer to itself:
+ %v: <*>{1 <*>}
+ %+v: <*>(0xf84003e260){ui8:1 c:<*>(0xf84003e260)}
+ %#v: (*main.circular){ui8:(uint8)1 c:(*main.circular)}
+ %#+v: (*main.circular)(0xf84003e260){ui8:(uint8)1 c:(*main.circular)(0xf84003e260)}
+
+See the Printf example for details on the setup of variables being shown
+here.
+
+Errors
+
+Since it is possible for custom Stringer/error interfaces to panic, spew
+detects them and handles them internally by printing the panic information
+inline with the output. Since spew is intended to provide deep pretty printing
+capabilities on structures, it intentionally does not return any errors.
+*/
+package spew
diff --git a/vendor/github.com/davecgh/go-spew/spew/dump.go b/vendor/github.com/davecgh/go-spew/spew/dump.go
new file mode 100644
index 0000000..f78d89f
--- /dev/null
+++ b/vendor/github.com/davecgh/go-spew/spew/dump.go
@@ -0,0 +1,509 @@
+/*
+ * Copyright (c) 2013-2016 Dave Collins
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+package spew
+
+import (
+ "bytes"
+ "encoding/hex"
+ "fmt"
+ "io"
+ "os"
+ "reflect"
+ "regexp"
+ "strconv"
+ "strings"
+)
+
+var (
+ // uint8Type is a reflect.Type representing a uint8. It is used to
+ // convert cgo types to uint8 slices for hexdumping.
+ uint8Type = reflect.TypeOf(uint8(0))
+
+ // cCharRE is a regular expression that matches a cgo char.
+ // It is used to detect character arrays to hexdump them.
+ cCharRE = regexp.MustCompile(`^.*\._Ctype_char$`)
+
+ // cUnsignedCharRE is a regular expression that matches a cgo unsigned
+ // char. It is used to detect unsigned character arrays to hexdump
+ // them.
+ cUnsignedCharRE = regexp.MustCompile(`^.*\._Ctype_unsignedchar$`)
+
+ // cUint8tCharRE is a regular expression that matches a cgo uint8_t.
+ // It is used to detect uint8_t arrays to hexdump them.
+ cUint8tCharRE = regexp.MustCompile(`^.*\._Ctype_uint8_t$`)
+)
+
+// dumpState contains information about the state of a dump operation.
+type dumpState struct {
+ w io.Writer
+ depth int
+ pointers map[uintptr]int
+ ignoreNextType bool
+ ignoreNextIndent bool
+ cs *ConfigState
+}
+
+// indent performs indentation according to the depth level and cs.Indent
+// option.
+func (d *dumpState) indent() {
+ if d.ignoreNextIndent {
+ d.ignoreNextIndent = false
+ return
+ }
+ d.w.Write(bytes.Repeat([]byte(d.cs.Indent), d.depth))
+}
+
+// unpackValue returns values inside of non-nil interfaces when possible.
+// This is useful for data types like structs, arrays, slices, and maps which
+// can contain varying types packed inside an interface.
+func (d *dumpState) unpackValue(v reflect.Value) reflect.Value {
+ if v.Kind() == reflect.Interface && !v.IsNil() {
+ v = v.Elem()
+ }
+ return v
+}
+
+// dumpPtr handles formatting of pointers by indirecting them as necessary.
+func (d *dumpState) dumpPtr(v reflect.Value) {
+ // Remove pointers at or below the current depth from map used to detect
+ // circular refs.
+ for k, depth := range d.pointers {
+ if depth >= d.depth {
+ delete(d.pointers, k)
+ }
+ }
+
+ // Keep list of all dereferenced pointers to show later.
+ pointerChain := make([]uintptr, 0)
+
+ // Figure out how many levels of indirection there are by dereferencing
+ // pointers and unpacking interfaces down the chain while detecting circular
+ // references.
+ nilFound := false
+ cycleFound := false
+ indirects := 0
+ ve := v
+ for ve.Kind() == reflect.Ptr {
+ if ve.IsNil() {
+ nilFound = true
+ break
+ }
+ indirects++
+ addr := ve.Pointer()
+ pointerChain = append(pointerChain, addr)
+ if pd, ok := d.pointers[addr]; ok && pd < d.depth {
+ cycleFound = true
+ indirects--
+ break
+ }
+ d.pointers[addr] = d.depth
+
+ ve = ve.Elem()
+ if ve.Kind() == reflect.Interface {
+ if ve.IsNil() {
+ nilFound = true
+ break
+ }
+ ve = ve.Elem()
+ }
+ }
+
+ // Display type information.
+ d.w.Write(openParenBytes)
+ d.w.Write(bytes.Repeat(asteriskBytes, indirects))
+ d.w.Write([]byte(ve.Type().String()))
+ d.w.Write(closeParenBytes)
+
+ // Display pointer information.
+ if !d.cs.DisablePointerAddresses && len(pointerChain) > 0 {
+ d.w.Write(openParenBytes)
+ for i, addr := range pointerChain {
+ if i > 0 {
+ d.w.Write(pointerChainBytes)
+ }
+ printHexPtr(d.w, addr)
+ }
+ d.w.Write(closeParenBytes)
+ }
+
+ // Display dereferenced value.
+ d.w.Write(openParenBytes)
+ switch {
+ case nilFound:
+ d.w.Write(nilAngleBytes)
+
+ case cycleFound:
+ d.w.Write(circularBytes)
+
+ default:
+ d.ignoreNextType = true
+ d.dump(ve)
+ }
+ d.w.Write(closeParenBytes)
+}
+
+// dumpSlice handles formatting of arrays and slices. Byte (uint8 under
+// reflection) arrays and slices are dumped in hexdump -C fashion.
+func (d *dumpState) dumpSlice(v reflect.Value) {
+ // Determine whether this type should be hex dumped or not. Also,
+ // for types which should be hexdumped, try to use the underlying data
+ // first, then fall back to trying to convert them to a uint8 slice.
+ var buf []uint8
+ doConvert := false
+ doHexDump := false
+ numEntries := v.Len()
+ if numEntries > 0 {
+ vt := v.Index(0).Type()
+ vts := vt.String()
+ switch {
+ // C types that need to be converted.
+ case cCharRE.MatchString(vts):
+ fallthrough
+ case cUnsignedCharRE.MatchString(vts):
+ fallthrough
+ case cUint8tCharRE.MatchString(vts):
+ doConvert = true
+
+ // Try to use existing uint8 slices and fall back to converting
+ // and copying if that fails.
+ case vt.Kind() == reflect.Uint8:
+ // We need an addressable interface to convert the type
+ // to a byte slice. However, the reflect package won't
+ // give us an interface on certain things like
+ // unexported struct fields in order to enforce
+ // visibility rules. We use unsafe, when available, to
+ // bypass these restrictions since this package does not
+ // mutate the values.
+ vs := v
+ if !vs.CanInterface() || !vs.CanAddr() {
+ vs = unsafeReflectValue(vs)
+ }
+ if !UnsafeDisabled {
+ vs = vs.Slice(0, numEntries)
+
+ // Use the existing uint8 slice if it can be
+ // type asserted.
+ iface := vs.Interface()
+ if slice, ok := iface.([]uint8); ok {
+ buf = slice
+ doHexDump = true
+ break
+ }
+ }
+
+ // The underlying data needs to be converted if it can't
+ // be type asserted to a uint8 slice.
+ doConvert = true
+ }
+
+ // Copy and convert the underlying type if needed.
+ if doConvert && vt.ConvertibleTo(uint8Type) {
+ // Convert and copy each element into a uint8 byte
+ // slice.
+ buf = make([]uint8, numEntries)
+ for i := 0; i < numEntries; i++ {
+ vv := v.Index(i)
+ buf[i] = uint8(vv.Convert(uint8Type).Uint())
+ }
+ doHexDump = true
+ }
+ }
+
+ // Hexdump the entire slice as needed.
+ if doHexDump {
+ indent := strings.Repeat(d.cs.Indent, d.depth)
+ str := indent + hex.Dump(buf)
+ str = strings.Replace(str, "\n", "\n"+indent, -1)
+ str = strings.TrimRight(str, d.cs.Indent)
+ d.w.Write([]byte(str))
+ return
+ }
+
+ // Recursively call dump for each item.
+ for i := 0; i < numEntries; i++ {
+ d.dump(d.unpackValue(v.Index(i)))
+ if i < (numEntries - 1) {
+ d.w.Write(commaNewlineBytes)
+ } else {
+ d.w.Write(newlineBytes)
+ }
+ }
+}
+
+// dump is the main workhorse for dumping a value. It uses the passed reflect
+// value to figure out what kind of object we are dealing with and formats it
+// appropriately. It is a recursive function, however circular data structures
+// are detected and handled properly.
+func (d *dumpState) dump(v reflect.Value) {
+ // Handle invalid reflect values immediately.
+ kind := v.Kind()
+ if kind == reflect.Invalid {
+ d.w.Write(invalidAngleBytes)
+ return
+ }
+
+ // Handle pointers specially.
+ if kind == reflect.Ptr {
+ d.indent()
+ d.dumpPtr(v)
+ return
+ }
+
+ // Print type information unless already handled elsewhere.
+ if !d.ignoreNextType {
+ d.indent()
+ d.w.Write(openParenBytes)
+ d.w.Write([]byte(v.Type().String()))
+ d.w.Write(closeParenBytes)
+ d.w.Write(spaceBytes)
+ }
+ d.ignoreNextType = false
+
+ // Display length and capacity if the built-in len and cap functions
+ // work with the value's kind and the len/cap itself is non-zero.
+ valueLen, valueCap := 0, 0
+ switch v.Kind() {
+ case reflect.Array, reflect.Slice, reflect.Chan:
+ valueLen, valueCap = v.Len(), v.Cap()
+ case reflect.Map, reflect.String:
+ valueLen = v.Len()
+ }
+ if valueLen != 0 || !d.cs.DisableCapacities && valueCap != 0 {
+ d.w.Write(openParenBytes)
+ if valueLen != 0 {
+ d.w.Write(lenEqualsBytes)
+ printInt(d.w, int64(valueLen), 10)
+ }
+ if !d.cs.DisableCapacities && valueCap != 0 {
+ if valueLen != 0 {
+ d.w.Write(spaceBytes)
+ }
+ d.w.Write(capEqualsBytes)
+ printInt(d.w, int64(valueCap), 10)
+ }
+ d.w.Write(closeParenBytes)
+ d.w.Write(spaceBytes)
+ }
+
+ // Call Stringer/error interfaces if they exist and the handle methods flag
+ // is enabled
+ if !d.cs.DisableMethods {
+ if (kind != reflect.Invalid) && (kind != reflect.Interface) {
+ if handled := handleMethods(d.cs, d.w, v); handled {
+ return
+ }
+ }
+ }
+
+ switch kind {
+ case reflect.Invalid:
+ // Do nothing. We should never get here since invalid has already
+ // been handled above.
+
+ case reflect.Bool:
+ printBool(d.w, v.Bool())
+
+ case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int:
+ printInt(d.w, v.Int(), 10)
+
+ case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint:
+ printUint(d.w, v.Uint(), 10)
+
+ case reflect.Float32:
+ printFloat(d.w, v.Float(), 32)
+
+ case reflect.Float64:
+ printFloat(d.w, v.Float(), 64)
+
+ case reflect.Complex64:
+ printComplex(d.w, v.Complex(), 32)
+
+ case reflect.Complex128:
+ printComplex(d.w, v.Complex(), 64)
+
+ case reflect.Slice:
+ if v.IsNil() {
+ d.w.Write(nilAngleBytes)
+ break
+ }
+ fallthrough
+
+ case reflect.Array:
+ d.w.Write(openBraceNewlineBytes)
+ d.depth++
+ if (d.cs.MaxDepth != 0) && (d.depth > d.cs.MaxDepth) {
+ d.indent()
+ d.w.Write(maxNewlineBytes)
+ } else {
+ d.dumpSlice(v)
+ }
+ d.depth--
+ d.indent()
+ d.w.Write(closeBraceBytes)
+
+ case reflect.String:
+ d.w.Write([]byte(strconv.Quote(v.String())))
+
+ case reflect.Interface:
+ // The only time we should get here is for nil interfaces due to
+ // unpackValue calls.
+ if v.IsNil() {
+ d.w.Write(nilAngleBytes)
+ }
+
+ case reflect.Ptr:
+ // Do nothing. We should never get here since pointers have already
+ // been handled above.
+
+ case reflect.Map:
+ // nil maps should be indicated as different than empty maps
+ if v.IsNil() {
+ d.w.Write(nilAngleBytes)
+ break
+ }
+
+ d.w.Write(openBraceNewlineBytes)
+ d.depth++
+ if (d.cs.MaxDepth != 0) && (d.depth > d.cs.MaxDepth) {
+ d.indent()
+ d.w.Write(maxNewlineBytes)
+ } else {
+ numEntries := v.Len()
+ keys := v.MapKeys()
+ if d.cs.SortKeys {
+ sortValues(keys, d.cs)
+ }
+ for i, key := range keys {
+ d.dump(d.unpackValue(key))
+ d.w.Write(colonSpaceBytes)
+ d.ignoreNextIndent = true
+ d.dump(d.unpackValue(v.MapIndex(key)))
+ if i < (numEntries - 1) {
+ d.w.Write(commaNewlineBytes)
+ } else {
+ d.w.Write(newlineBytes)
+ }
+ }
+ }
+ d.depth--
+ d.indent()
+ d.w.Write(closeBraceBytes)
+
+ case reflect.Struct:
+ d.w.Write(openBraceNewlineBytes)
+ d.depth++
+ if (d.cs.MaxDepth != 0) && (d.depth > d.cs.MaxDepth) {
+ d.indent()
+ d.w.Write(maxNewlineBytes)
+ } else {
+ vt := v.Type()
+ numFields := v.NumField()
+ for i := 0; i < numFields; i++ {
+ d.indent()
+ vtf := vt.Field(i)
+ d.w.Write([]byte(vtf.Name))
+ d.w.Write(colonSpaceBytes)
+ d.ignoreNextIndent = true
+ d.dump(d.unpackValue(v.Field(i)))
+ if i < (numFields - 1) {
+ d.w.Write(commaNewlineBytes)
+ } else {
+ d.w.Write(newlineBytes)
+ }
+ }
+ }
+ d.depth--
+ d.indent()
+ d.w.Write(closeBraceBytes)
+
+ case reflect.Uintptr:
+ printHexPtr(d.w, uintptr(v.Uint()))
+
+ case reflect.UnsafePointer, reflect.Chan, reflect.Func:
+ printHexPtr(d.w, v.Pointer())
+
+ // There were not any other types at the time this code was written, but
+ // fall back to letting the default fmt package handle it in case any new
+ // types are added.
+ default:
+ if v.CanInterface() {
+ fmt.Fprintf(d.w, "%v", v.Interface())
+ } else {
+ fmt.Fprintf(d.w, "%v", v.String())
+ }
+ }
+}
+
+// fdump is a helper function to consolidate the logic from the various public
+// methods which take varying writers and config states.
+func fdump(cs *ConfigState, w io.Writer, a ...interface{}) {
+ for _, arg := range a {
+ if arg == nil {
+ w.Write(interfaceBytes)
+ w.Write(spaceBytes)
+ w.Write(nilAngleBytes)
+ w.Write(newlineBytes)
+ continue
+ }
+
+ d := dumpState{w: w, cs: cs}
+ d.pointers = make(map[uintptr]int)
+ d.dump(reflect.ValueOf(arg))
+ d.w.Write(newlineBytes)
+ }
+}
+
+// Fdump formats and displays the passed arguments to io.Writer w. It formats
+// exactly the same as Dump.
+func Fdump(w io.Writer, a ...interface{}) {
+ fdump(&Config, w, a...)
+}
+
+// Sdump returns a string with the passed arguments formatted exactly the same
+// as Dump.
+func Sdump(a ...interface{}) string {
+ var buf bytes.Buffer
+ fdump(&Config, &buf, a...)
+ return buf.String()
+}
+
+/*
+Dump displays the passed parameters to standard out with newlines, customizable
+indentation, and additional debug information such as complete types and all
+pointer addresses used to indirect to the final value. It provides the
+following features over the built-in printing facilities provided by the fmt
+package:
+
+ * Pointers are dereferenced and followed
+ * Circular data structures are detected and handled properly
+ * Custom Stringer/error interfaces are optionally invoked, including
+ on unexported types
+ * Custom types which only implement the Stringer/error interfaces via
+ a pointer receiver are optionally invoked when passing non-pointer
+ variables
+ * Byte arrays and slices are dumped like the hexdump -C command which
+ includes offsets, byte values in hex, and ASCII output
+
+The configuration options are controlled by an exported package global,
+spew.Config. See ConfigState for options documentation.
+
+See Fdump if you would prefer dumping to an arbitrary io.Writer or Sdump to
+get the formatted result as a string.
+*/
+func Dump(a ...interface{}) {
+ fdump(&Config, os.Stdout, a...)
+}
diff --git a/vendor/github.com/davecgh/go-spew/spew/format.go b/vendor/github.com/davecgh/go-spew/spew/format.go
new file mode 100644
index 0000000..b04edb7
--- /dev/null
+++ b/vendor/github.com/davecgh/go-spew/spew/format.go
@@ -0,0 +1,419 @@
+/*
+ * Copyright (c) 2013-2016 Dave Collins
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+package spew
+
+import (
+ "bytes"
+ "fmt"
+ "reflect"
+ "strconv"
+ "strings"
+)
+
+// supportedFlags is a list of all the character flags supported by fmt package.
+const supportedFlags = "0-+# "
+
+// formatState implements the fmt.Formatter interface and contains information
+// about the state of a formatting operation. The NewFormatter function can
+// be used to get a new Formatter which can be used directly as arguments
+// in standard fmt package printing calls.
+type formatState struct {
+ value interface{}
+ fs fmt.State
+ depth int
+ pointers map[uintptr]int
+ ignoreNextType bool
+ cs *ConfigState
+}
+
+// buildDefaultFormat recreates the original format string without precision
+// and width information to pass in to fmt.Sprintf in the case of an
+// unrecognized type. Unless new types are added to the language, this
+// function won't ever be called.
+func (f *formatState) buildDefaultFormat() (format string) {
+ buf := bytes.NewBuffer(percentBytes)
+
+ for _, flag := range supportedFlags {
+ if f.fs.Flag(int(flag)) {
+ buf.WriteRune(flag)
+ }
+ }
+
+ buf.WriteRune('v')
+
+ format = buf.String()
+ return format
+}
+
+// constructOrigFormat recreates the original format string including precision
+// and width information to pass along to the standard fmt package. This allows
+// automatic deferral of all format strings this package doesn't support.
+func (f *formatState) constructOrigFormat(verb rune) (format string) {
+ buf := bytes.NewBuffer(percentBytes)
+
+ for _, flag := range supportedFlags {
+ if f.fs.Flag(int(flag)) {
+ buf.WriteRune(flag)
+ }
+ }
+
+ if width, ok := f.fs.Width(); ok {
+ buf.WriteString(strconv.Itoa(width))
+ }
+
+ if precision, ok := f.fs.Precision(); ok {
+ buf.Write(precisionBytes)
+ buf.WriteString(strconv.Itoa(precision))
+ }
+
+ buf.WriteRune(verb)
+
+ format = buf.String()
+ return format
+}
+
+// unpackValue returns values inside of non-nil interfaces when possible and
+// ensures that types for values which have been unpacked from an interface
+// are displayed when the show types flag is also set.
+// This is useful for data types like structs, arrays, slices, and maps which
+// can contain varying types packed inside an interface.
+func (f *formatState) unpackValue(v reflect.Value) reflect.Value {
+ if v.Kind() == reflect.Interface {
+ f.ignoreNextType = false
+ if !v.IsNil() {
+ v = v.Elem()
+ }
+ }
+ return v
+}
+
+// formatPtr handles formatting of pointers by indirecting them as necessary.
+func (f *formatState) formatPtr(v reflect.Value) {
+ // Display nil if top level pointer is nil.
+ showTypes := f.fs.Flag('#')
+ if v.IsNil() && (!showTypes || f.ignoreNextType) {
+ f.fs.Write(nilAngleBytes)
+ return
+ }
+
+ // Remove pointers at or below the current depth from map used to detect
+ // circular refs.
+ for k, depth := range f.pointers {
+ if depth >= f.depth {
+ delete(f.pointers, k)
+ }
+ }
+
+ // Keep list of all dereferenced pointers to possibly show later.
+ pointerChain := make([]uintptr, 0)
+
+ // Figure out how many levels of indirection there are by derferencing
+ // pointers and unpacking interfaces down the chain while detecting circular
+ // references.
+ nilFound := false
+ cycleFound := false
+ indirects := 0
+ ve := v
+ for ve.Kind() == reflect.Ptr {
+ if ve.IsNil() {
+ nilFound = true
+ break
+ }
+ indirects++
+ addr := ve.Pointer()
+ pointerChain = append(pointerChain, addr)
+ if pd, ok := f.pointers[addr]; ok && pd < f.depth {
+ cycleFound = true
+ indirects--
+ break
+ }
+ f.pointers[addr] = f.depth
+
+ ve = ve.Elem()
+ if ve.Kind() == reflect.Interface {
+ if ve.IsNil() {
+ nilFound = true
+ break
+ }
+ ve = ve.Elem()
+ }
+ }
+
+ // Display type or indirection level depending on flags.
+ if showTypes && !f.ignoreNextType {
+ f.fs.Write(openParenBytes)
+ f.fs.Write(bytes.Repeat(asteriskBytes, indirects))
+ f.fs.Write([]byte(ve.Type().String()))
+ f.fs.Write(closeParenBytes)
+ } else {
+ if nilFound || cycleFound {
+ indirects += strings.Count(ve.Type().String(), "*")
+ }
+ f.fs.Write(openAngleBytes)
+ f.fs.Write([]byte(strings.Repeat("*", indirects)))
+ f.fs.Write(closeAngleBytes)
+ }
+
+ // Display pointer information depending on flags.
+ if f.fs.Flag('+') && (len(pointerChain) > 0) {
+ f.fs.Write(openParenBytes)
+ for i, addr := range pointerChain {
+ if i > 0 {
+ f.fs.Write(pointerChainBytes)
+ }
+ printHexPtr(f.fs, addr)
+ }
+ f.fs.Write(closeParenBytes)
+ }
+
+ // Display dereferenced value.
+ switch {
+ case nilFound:
+ f.fs.Write(nilAngleBytes)
+
+ case cycleFound:
+ f.fs.Write(circularShortBytes)
+
+ default:
+ f.ignoreNextType = true
+ f.format(ve)
+ }
+}
+
+// format is the main workhorse for providing the Formatter interface. It
+// uses the passed reflect value to figure out what kind of object we are
+// dealing with and formats it appropriately. It is a recursive function,
+// however circular data structures are detected and handled properly.
+func (f *formatState) format(v reflect.Value) {
+ // Handle invalid reflect values immediately.
+ kind := v.Kind()
+ if kind == reflect.Invalid {
+ f.fs.Write(invalidAngleBytes)
+ return
+ }
+
+ // Handle pointers specially.
+ if kind == reflect.Ptr {
+ f.formatPtr(v)
+ return
+ }
+
+ // Print type information unless already handled elsewhere.
+ if !f.ignoreNextType && f.fs.Flag('#') {
+ f.fs.Write(openParenBytes)
+ f.fs.Write([]byte(v.Type().String()))
+ f.fs.Write(closeParenBytes)
+ }
+ f.ignoreNextType = false
+
+ // Call Stringer/error interfaces if they exist and the handle methods
+ // flag is enabled.
+ if !f.cs.DisableMethods {
+ if (kind != reflect.Invalid) && (kind != reflect.Interface) {
+ if handled := handleMethods(f.cs, f.fs, v); handled {
+ return
+ }
+ }
+ }
+
+ switch kind {
+ case reflect.Invalid:
+ // Do nothing. We should never get here since invalid has already
+ // been handled above.
+
+ case reflect.Bool:
+ printBool(f.fs, v.Bool())
+
+ case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int:
+ printInt(f.fs, v.Int(), 10)
+
+ case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint:
+ printUint(f.fs, v.Uint(), 10)
+
+ case reflect.Float32:
+ printFloat(f.fs, v.Float(), 32)
+
+ case reflect.Float64:
+ printFloat(f.fs, v.Float(), 64)
+
+ case reflect.Complex64:
+ printComplex(f.fs, v.Complex(), 32)
+
+ case reflect.Complex128:
+ printComplex(f.fs, v.Complex(), 64)
+
+ case reflect.Slice:
+ if v.IsNil() {
+ f.fs.Write(nilAngleBytes)
+ break
+ }
+ fallthrough
+
+ case reflect.Array:
+ f.fs.Write(openBracketBytes)
+ f.depth++
+ if (f.cs.MaxDepth != 0) && (f.depth > f.cs.MaxDepth) {
+ f.fs.Write(maxShortBytes)
+ } else {
+ numEntries := v.Len()
+ for i := 0; i < numEntries; i++ {
+ if i > 0 {
+ f.fs.Write(spaceBytes)
+ }
+ f.ignoreNextType = true
+ f.format(f.unpackValue(v.Index(i)))
+ }
+ }
+ f.depth--
+ f.fs.Write(closeBracketBytes)
+
+ case reflect.String:
+ f.fs.Write([]byte(v.String()))
+
+ case reflect.Interface:
+ // The only time we should get here is for nil interfaces due to
+ // unpackValue calls.
+ if v.IsNil() {
+ f.fs.Write(nilAngleBytes)
+ }
+
+ case reflect.Ptr:
+ // Do nothing. We should never get here since pointers have already
+ // been handled above.
+
+ case reflect.Map:
+ // nil maps should be indicated as different than empty maps
+ if v.IsNil() {
+ f.fs.Write(nilAngleBytes)
+ break
+ }
+
+ f.fs.Write(openMapBytes)
+ f.depth++
+ if (f.cs.MaxDepth != 0) && (f.depth > f.cs.MaxDepth) {
+ f.fs.Write(maxShortBytes)
+ } else {
+ keys := v.MapKeys()
+ if f.cs.SortKeys {
+ sortValues(keys, f.cs)
+ }
+ for i, key := range keys {
+ if i > 0 {
+ f.fs.Write(spaceBytes)
+ }
+ f.ignoreNextType = true
+ f.format(f.unpackValue(key))
+ f.fs.Write(colonBytes)
+ f.ignoreNextType = true
+ f.format(f.unpackValue(v.MapIndex(key)))
+ }
+ }
+ f.depth--
+ f.fs.Write(closeMapBytes)
+
+ case reflect.Struct:
+ numFields := v.NumField()
+ f.fs.Write(openBraceBytes)
+ f.depth++
+ if (f.cs.MaxDepth != 0) && (f.depth > f.cs.MaxDepth) {
+ f.fs.Write(maxShortBytes)
+ } else {
+ vt := v.Type()
+ for i := 0; i < numFields; i++ {
+ if i > 0 {
+ f.fs.Write(spaceBytes)
+ }
+ vtf := vt.Field(i)
+ if f.fs.Flag('+') || f.fs.Flag('#') {
+ f.fs.Write([]byte(vtf.Name))
+ f.fs.Write(colonBytes)
+ }
+ f.format(f.unpackValue(v.Field(i)))
+ }
+ }
+ f.depth--
+ f.fs.Write(closeBraceBytes)
+
+ case reflect.Uintptr:
+ printHexPtr(f.fs, uintptr(v.Uint()))
+
+ case reflect.UnsafePointer, reflect.Chan, reflect.Func:
+ printHexPtr(f.fs, v.Pointer())
+
+ // There were not any other types at the time this code was written, but
+ // fall back to letting the default fmt package handle it if any get added.
+ default:
+ format := f.buildDefaultFormat()
+ if v.CanInterface() {
+ fmt.Fprintf(f.fs, format, v.Interface())
+ } else {
+ fmt.Fprintf(f.fs, format, v.String())
+ }
+ }
+}
+
+// Format satisfies the fmt.Formatter interface. See NewFormatter for usage
+// details.
+func (f *formatState) Format(fs fmt.State, verb rune) {
+ f.fs = fs
+
+ // Use standard formatting for verbs that are not v.
+ if verb != 'v' {
+ format := f.constructOrigFormat(verb)
+ fmt.Fprintf(fs, format, f.value)
+ return
+ }
+
+ if f.value == nil {
+ if fs.Flag('#') {
+ fs.Write(interfaceBytes)
+ }
+ fs.Write(nilAngleBytes)
+ return
+ }
+
+ f.format(reflect.ValueOf(f.value))
+}
+
+// newFormatter is a helper function to consolidate the logic from the various
+// public methods which take varying config states.
+func newFormatter(cs *ConfigState, v interface{}) fmt.Formatter {
+ fs := &formatState{value: v, cs: cs}
+ fs.pointers = make(map[uintptr]int)
+ return fs
+}
+
+/*
+NewFormatter returns a custom formatter that satisfies the fmt.Formatter
+interface. As a result, it integrates cleanly with standard fmt package
+printing functions. The formatter is useful for inline printing of smaller data
+types similar to the standard %v format specifier.
+
+The custom formatter only responds to the %v (most compact), %+v (adds pointer
+addresses), %#v (adds types), or %#+v (adds types and pointer addresses) verb
+combinations. Any other verbs such as %x and %q will be sent to the the
+standard fmt package for formatting. In addition, the custom formatter ignores
+the width and precision arguments (however they will still work on the format
+specifiers not handled by the custom formatter).
+
+Typically this function shouldn't be called directly. It is much easier to make
+use of the custom formatter by calling one of the convenience functions such as
+Printf, Println, or Fprintf.
+*/
+func NewFormatter(v interface{}) fmt.Formatter {
+ return newFormatter(&Config, v)
+}
diff --git a/vendor/github.com/davecgh/go-spew/spew/spew.go b/vendor/github.com/davecgh/go-spew/spew/spew.go
new file mode 100644
index 0000000..32c0e33
--- /dev/null
+++ b/vendor/github.com/davecgh/go-spew/spew/spew.go
@@ -0,0 +1,148 @@
+/*
+ * Copyright (c) 2013-2016 Dave Collins
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+package spew
+
+import (
+ "fmt"
+ "io"
+)
+
+// Errorf is a wrapper for fmt.Errorf that treats each argument as if it were
+// passed with a default Formatter interface returned by NewFormatter. It
+// returns the formatted string as a value that satisfies error. See
+// NewFormatter for formatting details.
+//
+// This function is shorthand for the following syntax:
+//
+// fmt.Errorf(format, spew.NewFormatter(a), spew.NewFormatter(b))
+func Errorf(format string, a ...interface{}) (err error) {
+ return fmt.Errorf(format, convertArgs(a)...)
+}
+
+// Fprint is a wrapper for fmt.Fprint that treats each argument as if it were
+// passed with a default Formatter interface returned by NewFormatter. It
+// returns the number of bytes written and any write error encountered. See
+// NewFormatter for formatting details.
+//
+// This function is shorthand for the following syntax:
+//
+// fmt.Fprint(w, spew.NewFormatter(a), spew.NewFormatter(b))
+func Fprint(w io.Writer, a ...interface{}) (n int, err error) {
+ return fmt.Fprint(w, convertArgs(a)...)
+}
+
+// Fprintf is a wrapper for fmt.Fprintf that treats each argument as if it were
+// passed with a default Formatter interface returned by NewFormatter. It
+// returns the number of bytes written and any write error encountered. See
+// NewFormatter for formatting details.
+//
+// This function is shorthand for the following syntax:
+//
+// fmt.Fprintf(w, format, spew.NewFormatter(a), spew.NewFormatter(b))
+func Fprintf(w io.Writer, format string, a ...interface{}) (n int, err error) {
+ return fmt.Fprintf(w, format, convertArgs(a)...)
+}
+
+// Fprintln is a wrapper for fmt.Fprintln that treats each argument as if it
+// passed with a default Formatter interface returned by NewFormatter. See
+// NewFormatter for formatting details.
+//
+// This function is shorthand for the following syntax:
+//
+// fmt.Fprintln(w, spew.NewFormatter(a), spew.NewFormatter(b))
+func Fprintln(w io.Writer, a ...interface{}) (n int, err error) {
+ return fmt.Fprintln(w, convertArgs(a)...)
+}
+
+// Print is a wrapper for fmt.Print that treats each argument as if it were
+// passed with a default Formatter interface returned by NewFormatter. It
+// returns the number of bytes written and any write error encountered. See
+// NewFormatter for formatting details.
+//
+// This function is shorthand for the following syntax:
+//
+// fmt.Print(spew.NewFormatter(a), spew.NewFormatter(b))
+func Print(a ...interface{}) (n int, err error) {
+ return fmt.Print(convertArgs(a)...)
+}
+
+// Printf is a wrapper for fmt.Printf that treats each argument as if it were
+// passed with a default Formatter interface returned by NewFormatter. It
+// returns the number of bytes written and any write error encountered. See
+// NewFormatter for formatting details.
+//
+// This function is shorthand for the following syntax:
+//
+// fmt.Printf(format, spew.NewFormatter(a), spew.NewFormatter(b))
+func Printf(format string, a ...interface{}) (n int, err error) {
+ return fmt.Printf(format, convertArgs(a)...)
+}
+
+// Println is a wrapper for fmt.Println that treats each argument as if it were
+// passed with a default Formatter interface returned by NewFormatter. It
+// returns the number of bytes written and any write error encountered. See
+// NewFormatter for formatting details.
+//
+// This function is shorthand for the following syntax:
+//
+// fmt.Println(spew.NewFormatter(a), spew.NewFormatter(b))
+func Println(a ...interface{}) (n int, err error) {
+ return fmt.Println(convertArgs(a)...)
+}
+
+// Sprint is a wrapper for fmt.Sprint that treats each argument as if it were
+// passed with a default Formatter interface returned by NewFormatter. It
+// returns the resulting string. See NewFormatter for formatting details.
+//
+// This function is shorthand for the following syntax:
+//
+// fmt.Sprint(spew.NewFormatter(a), spew.NewFormatter(b))
+func Sprint(a ...interface{}) string {
+ return fmt.Sprint(convertArgs(a)...)
+}
+
+// Sprintf is a wrapper for fmt.Sprintf that treats each argument as if it were
+// passed with a default Formatter interface returned by NewFormatter. It
+// returns the resulting string. See NewFormatter for formatting details.
+//
+// This function is shorthand for the following syntax:
+//
+// fmt.Sprintf(format, spew.NewFormatter(a), spew.NewFormatter(b))
+func Sprintf(format string, a ...interface{}) string {
+ return fmt.Sprintf(format, convertArgs(a)...)
+}
+
+// Sprintln is a wrapper for fmt.Sprintln that treats each argument as if it
+// were passed with a default Formatter interface returned by NewFormatter. It
+// returns the resulting string. See NewFormatter for formatting details.
+//
+// This function is shorthand for the following syntax:
+//
+// fmt.Sprintln(spew.NewFormatter(a), spew.NewFormatter(b))
+func Sprintln(a ...interface{}) string {
+ return fmt.Sprintln(convertArgs(a)...)
+}
+
+// convertArgs accepts a slice of arguments and returns a slice of the same
+// length with each argument converted to a default spew Formatter interface.
+func convertArgs(args []interface{}) (formatters []interface{}) {
+ formatters = make([]interface{}, len(args))
+ for index, arg := range args {
+ formatters[index] = NewFormatter(arg)
+ }
+ return formatters
+}
diff --git a/vendor/github.com/google/go-github/v33/github/code-scanning.go b/vendor/github.com/google/go-github/v33/github/code-scanning.go
deleted file mode 100644
index 9eb711c..0000000
--- a/vendor/github.com/google/go-github/v33/github/code-scanning.go
+++ /dev/null
@@ -1,117 +0,0 @@
-// Copyright 2020 The go-github AUTHORS. All rights reserved.
-//
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-package github
-
-import (
- "context"
- "fmt"
- "strconv"
- "strings"
-)
-
-// CodeScanningService handles communication with the code scanning related
-// methods of the GitHub API.
-//
-// GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/code-scanning/
-type CodeScanningService service
-
-type Alert struct {
- RuleID *string `json:"rule_id,omitempty"`
- RuleSeverity *string `json:"rule_severity,omitempty"`
- RuleDescription *string `json:"rule_description,omitempty"`
- Tool *string `json:"tool,omitempty"`
- CreatedAt *Timestamp `json:"created_at,omitempty"`
- Open *bool `json:"open,omitempty"`
- ClosedBy *User `json:"closed_by,omitempty"`
- ClosedAt *Timestamp `json:"closed_at,omitempty"`
- URL *string `json:"url,omitempty"`
- HTMLURL *string `json:"html_url,omitempty"`
-}
-
-// ID returns the ID associated with an alert. It is the number at the end of the security alert's URL.
-func (a *Alert) ID() int64 {
- if a == nil {
- return 0
- }
-
- s := a.GetHTMLURL()
-
- // Check for an ID to parse at the end of the url
- if i := strings.LastIndex(s, "/"); i >= 0 {
- s = s[i+1:]
- }
-
- // Return the alert ID as a 64-bit integer. Unable to convert or out of range returns 0.
- id, err := strconv.ParseInt(s, 10, 64)
- if err != nil {
- return 0
- }
-
- return id
-}
-
-// AlertListOptions specifies optional parameters to the CodeScanningService.ListAlerts
-// method.
-type AlertListOptions struct {
- // State of the code scanning alerts to list. Set to closed to list only closed code scanning alerts. Default: open
- State string `url:"state,omitempty"`
-
- // Return code scanning alerts for a specific branch reference. The ref must be formatted as heads/.
- Ref string `url:"ref,omitempty"`
-}
-
-// ListAlertsForRepo lists code scanning alerts for a repository.
-//
-// Lists all open code scanning alerts for the default branch (usually master) and protected branches in a repository.
-// You must use an access token with the security_events scope to use this endpoint. GitHub Apps must have the security_events
-// read permission to use this endpoint.
-//
-// GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/code-scanning/#list-code-scanning-alerts-for-a-repository
-func (s *CodeScanningService) ListAlertsForRepo(ctx context.Context, owner, repo string, opts *AlertListOptions) ([]*Alert, *Response, error) {
- u := fmt.Sprintf("repos/%v/%v/code-scanning/alerts", owner, repo)
- u, err := addOptions(u, opts)
- if err != nil {
- return nil, nil, err
- }
-
- req, err := s.client.NewRequest("GET", u, nil)
- if err != nil {
- return nil, nil, err
- }
-
- var alerts []*Alert
- resp, err := s.client.Do(ctx, req, &alerts)
- if err != nil {
- return nil, resp, err
- }
-
- return alerts, resp, nil
-}
-
-// GetAlert gets a single code scanning alert for a repository.
-//
-// You must use an access token with the security_events scope to use this endpoint.
-// GitHub Apps must have the security_events read permission to use this endpoint.
-//
-// The security alert_id is the number at the end of the security alert's URL.
-//
-// GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/code-scanning/#get-a-code-scanning-alert
-func (s *CodeScanningService) GetAlert(ctx context.Context, owner, repo string, id int64) (*Alert, *Response, error) {
- u := fmt.Sprintf("repos/%v/%v/code-scanning/alerts/%v", owner, repo, id)
-
- req, err := s.client.NewRequest("GET", u, nil)
- if err != nil {
- return nil, nil, err
- }
-
- a := new(Alert)
- resp, err := s.client.Do(ctx, req, a)
- if err != nil {
- return nil, resp, err
- }
-
- return a, resp, nil
-}
diff --git a/vendor/github.com/google/go-github/v33/AUTHORS b/vendor/github.com/google/go-github/v43/AUTHORS
similarity index 82%
rename from vendor/github.com/google/go-github/v33/AUTHORS
rename to vendor/github.com/google/go-github/v43/AUTHORS
index cafdf91..80bd26d 100644
--- a/vendor/github.com/google/go-github/v33/AUTHORS
+++ b/vendor/github.com/google/go-github/v43/AUTHORS
@@ -13,6 +13,7 @@
413x
Abhinav Gupta
adrienzieba
+afdesk
Ahmed Hagy
Aidan Steele
Ainsley Chong
@@ -23,7 +24,10 @@ Alec Thomas
Aleks Clark
Alex Bramley
Alex Orr
+Alex Unger
Alexander Harkness
+Alexis Gauthiez
+Ali Farooq
Allen Sun
Amey Sakhadeo
Anders Janmyr
@@ -37,23 +41,31 @@ anjanashenoy
Anshuman Bhartiya
Antoine
Antoine Pelisse
+Anton Nguyen
Anubha Kushwaha
appilon
+aprp
Aravind
Arda Kuyumcu
Arıl Bozoluk
+Asier Marruedo
Austin Burdine
Austin Dizzy
+Azuka Okuleye
Ben Batha
Benjamen Keroack
Beshr Kayali
Beyang Liu
+Billy Keyes
Billy Lynch
Björn Häuser
+boljen
Brad Harris
Brad Moylan
Bradley Falzon
+Bradley McAllister
Brandon Cook
+Brett Logan
Brian Egizi
Bryan Boreham
Cami Diez
@@ -63,7 +75,9 @@ Carlos Tadeu Panato Junior
chandresh-pancholi
Charles Fenwick Elliott
Charlie Yan
+Chmouel Boudjnah
Chris King
+Chris Mc
Chris Raborg
Chris Roche
Chris Schaefer
@@ -74,18 +88,22 @@ Colin Misare
Craig Peterson
Cristian Maglie
Daehyeok Mun
+Daniel Lanner
Daniel Leavitt
Daniel Nilsson
Daoq
Dave Du Cros
Dave Henderson
+Dave Perrett
Dave Protasowski
David Deng
+David J. M. Karlsen
David Jannotta
David Ji
David Lopez Reyes
Davide Zipeto
Dennis Webb
+Derek Jobst
Dhi Aurrahman
Diego Lapiduz
Dmitri Shuralyov
@@ -93,14 +111,17 @@ dmnlk
Don Petersen
Doug Turner
Drew Fradette
+Dustin Deus
Eivind
Eli Uriegas
Elliott Beach
Emerson Wood
eperm
Erick Fejta
+Erik Nobel
erwinvaneyk
Evan Elias
+Fabian Holler
Fabrice
Felix Geisendörfer
Filippo Valsorda
@@ -127,15 +148,24 @@ haya14busa
Huy Tr
huydx
i2bskn
+Ikko Ashimine
Ioannis Georgoulas
Isao Jonas
+ishan upadhyay
isqua
+Jacob Valdemar
+Jake Krammer
+Jake White
Jameel Haffejee
James Cockbain
+James Loh
Jan Kosecki
+Jan Švábík
Javier Campanini
+Jef LeCompte
Jens Rantil
Jeremy Morris
+Jesse Haka
Jesse Newland
Jihoon Chung
Jimmi Dyson
@@ -143,11 +173,13 @@ Joan Saum
Joe Tsai
John Barton
John Engelman
+John Liu
Jordan Brockopp
Jordan Sussman
Joshua Bezaleel Abednego
JP Phillips
jpbelanger-mtl
+Juan
Juan Basso
Julien Garcia Gonzalez
Julien Rostand
@@ -160,14 +192,17 @@ Katrina Owen
Kautilya Tripathi
Keita Urashima
Kevin Burke
+Kirill
Konrad Malawski
Kookheon Kwon
Krzysztof Kowalczyk
Kshitij Saraogi
+Kumar Saurabh
kyokomi
Laurent Verdoïa
Liam Galvin
Lovro Mažgon
+Luca Campese
Lucas Alcantara
Luke Evers
Luke Kysow
@@ -175,32 +210,44 @@ Luke Roberts
Luke Young
lynn [they]
Maksim Zhylinski
+Marc Binder
+Marcelo Carlos
Mark Tareshawty
+Martin Holman
Martin-Louis Bright
Martins Sipenko
Marwan Sulaiman
Masayuki Izumi
Mat Geist
+Matin Rahmanian
Matt
Matt Brender
Matt Gaunt
Matt Landis
+Matt Moore
Maxime Bury
Michael Spiegel
Michael Tiller
Michał Glapa
+Michelangelo Morrillo
+Mukundan Senthil
+Munia Balayil
Nadav Kaner
Nathan VanBenschoten
Navaneeth Suresh
Neil O'Toole
Nick Miyake
+Nick Platt
Nick Spragg
Nikhita Raghunath
+Nilesh Singh
Noah Zoschke
ns-cweber
Ole Orhagen
Oleg Kovalov
Ondřej Kupka
+Ori Talmor
+Pablo Pérez Schröder
Palash Nigam
Panagiotis Moustafellos
Parham Alvani
@@ -213,6 +260,7 @@ Pete Wagner
Petr Shevtsov
Pierre Carrier
Piotr Zurek
+Piyush Chugh
Pratik Mallya
Qais Patankar
Quang Le Hong
@@ -228,15 +276,20 @@ Ravi Shekhar Jethani
RaviTeja Pothana
rc1140
Red Hat, Inc.
+Reetuparna Mukherjee
Reinier Timmer
+Renjith R
Ricco Førgaard
Rob Figueiredo
Rohit Upadhyay
Ronak Jain
+Ross Gustafson
Ruben Vereecken
+Russell Boley
Ryan Leung
Ryan Lower
Ryo Nakao
+Saaarah
Safwan Olaimat
Sahil Dua
saisi
@@ -251,6 +304,7 @@ Sebastian Mandrean
Sebastian Mæland Pedersen