Skip to content

Commit 699e523

Browse files
committed
feat!: add prometheus metrics support
- Add arguments `-prometheus-metrics-enabled` and `-prometheus-listen-addr` to control whether to expose metrics from the registry app. - The `ModuleStore` interface has received an extra method `Metrics()` to support store-specific metrics.
1 parent 034ca57 commit 699e523

File tree

8 files changed

+123
-16
lines changed

8 files changed

+123
-16
lines changed

cmd/terraform-registry/main.go

Lines changed: 53 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -25,23 +25,27 @@ import (
2525
"github.com/nrkno/terraform-registry/pkg/registry"
2626
"github.com/nrkno/terraform-registry/pkg/store/github"
2727
"github.com/nrkno/terraform-registry/pkg/store/s3"
28+
"github.com/prometheus/client_golang/prometheus"
29+
"github.com/prometheus/client_golang/prometheus/promhttp"
2830
"go.uber.org/zap"
2931
)
3032

3133
var (
32-
listenAddr string
33-
accessLogDisabled bool
34-
accessLogIgnoredPaths string
35-
authDisabled bool
36-
authTokensFile string
37-
envJSONFiles string
38-
tlsEnabled bool
39-
tlsCertFile string
40-
tlsKeyFile string
41-
storeType string
42-
logLevelStr string
43-
logFormatStr string
44-
printVersionInfo bool
34+
listenAddr string
35+
accessLogDisabled bool
36+
accessLogIgnoredPaths string
37+
authDisabled bool
38+
authTokensFile string
39+
envJSONFiles string
40+
tlsEnabled bool
41+
tlsCertFile string
42+
tlsKeyFile string
43+
storeType string
44+
prometheusListenAddr string
45+
prometheusMetricsEnabled bool
46+
logLevelStr string
47+
logFormatStr string
48+
printVersionInfo bool
4549

4650
S3Region string
4751
S3Bucket string
@@ -84,6 +88,8 @@ func init() {
8488
flag.StringVar(&logLevelStr, "log-level", "info", "Levels: debug, info, warn, error")
8589
flag.StringVar(&logFormatStr, "log-format", "console", "Formats: json, console")
8690
flag.BoolVar(&printVersionInfo, "version", false, "Print version info and exit")
91+
flag.StringVar(&prometheusListenAddr, "prometheus-listen-addr", ":9090", "Listen address where /metrics endpoint is exposed.")
92+
flag.BoolVar(&prometheusMetricsEnabled, "prometheus-metrics-enabled", false, "Enable the Prometheus metrics endpoint.")
8793

8894
flag.StringVar(&gitHubOwnerFilter, "github-owner-filter", "", "GitHub org/user repository filter")
8995
flag.StringVar(&gitHubTopicFilter, "github-topic-filter", "", "GitHub topic repository filter")
@@ -187,6 +193,13 @@ func main() {
187193
logger.Fatal("invalid store type", zap.String("selected", storeType))
188194
}
189195

196+
if prometheusMetricsEnabled {
197+
mux := http.NewServeMux()
198+
mux.Handle("/metrics", promhttp.Handler())
199+
prometheus.MustRegister(reg.Metrics())
200+
go prometheusListenAndServe(mux)
201+
}
202+
190203
listenAndServe(reg)
191204
}
192205

@@ -217,6 +230,33 @@ func listenAndServe(handler http.Handler) {
217230
}
218231
}
219232

233+
func prometheusListenAndServe(handler http.Handler) {
234+
srv := http.Server{
235+
Addr: prometheusListenAddr,
236+
Handler: handler,
237+
ReadTimeout: 3 * time.Second,
238+
ReadHeaderTimeout: 3 * time.Second,
239+
WriteTimeout: 3 * time.Second,
240+
IdleTimeout: 60 * time.Second, // keep-alive timeout
241+
}
242+
243+
logger.Info("starting Prometheus metrics HTTP server",
244+
zap.Bool("tls", tlsEnabled),
245+
zap.String("listenAddr", prometheusListenAddr),
246+
)
247+
if tlsEnabled {
248+
err := srv.ListenAndServeTLS(tlsCertFile, tlsKeyFile)
249+
logger.Panic("prometheusListenAndServe",
250+
zap.Errors("err", []error{err}),
251+
)
252+
} else {
253+
err := srv.ListenAndServe()
254+
logger.Panic("prometheusListenAndServe",
255+
zap.Errors("err", []error{err}),
256+
)
257+
}
258+
}
259+
220260
// watchFile reads the contents of the file at `filename`, first immediately, then at at every `interval`.
221261
// If and only if the file contents have changed since the last invocation of `callback` it is called again.
222262
// Note that the callback will always be called initially when this function is called.

go.mod

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,23 +16,31 @@ require (
1616
github.com/hashicorp/go-version v1.6.0
1717
github.com/matryer/is v1.4.1
1818
github.com/migueleliasweb/go-github-mock v0.0.23
19+
github.com/prometheus/client_golang v1.18.0
1920
github.com/stretchr/testify v1.8.4
2021
go.uber.org/zap v1.27.0
2122
golang.org/x/oauth2 v0.17.0
2223
)
2324

2425
require (
26+
github.com/beorn7/perks v1.0.1 // indirect
27+
github.com/cespare/xxhash/v2 v2.2.0 // indirect
2528
github.com/davecgh/go-spew v1.1.1 // indirect
2629
github.com/golang/protobuf v1.5.3 // indirect
2730
github.com/google/go-github/v59 v59.0.0 // indirect
2831
github.com/google/go-querystring v1.1.0 // indirect
2932
github.com/gorilla/mux v1.8.0 // indirect
3033
github.com/jmespath/go-jmespath v0.4.0 // indirect
34+
github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 // indirect
3135
github.com/pmezard/go-difflib v1.0.0 // indirect
36+
github.com/prometheus/client_model v0.5.0 // indirect
37+
github.com/prometheus/common v0.45.0 // indirect
38+
github.com/prometheus/procfs v0.12.0 // indirect
3239
github.com/stretchr/objx v0.5.0 // indirect
3340
go.uber.org/multierr v1.10.0 // indirect
3441
golang.org/x/crypto v0.19.0 // indirect
3542
golang.org/x/net v0.21.0 // indirect
43+
golang.org/x/sys v0.17.0 // indirect
3644
golang.org/x/time v0.3.0 // indirect
3745
google.golang.org/appengine v1.6.7 // indirect
3846
google.golang.org/protobuf v1.31.0 // indirect

go.sum

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
github.com/aws/aws-sdk-go v1.50.25 h1:vhiHtLYybv1Nhx3Kv18BBC6L0aPJHaG9aeEsr92W99c=
22
github.com/aws/aws-sdk-go v1.50.25/go.mod h1:LF8svs817+Nz+DmiMQKTO3ubZ/6IaTpq3TjupRn3Eqk=
3+
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
4+
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
5+
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
6+
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
37
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
48
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
59
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@@ -27,12 +31,28 @@ github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9Y
2731
github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
2832
github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8=
2933
github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=
34+
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
35+
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
36+
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
37+
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
3038
github.com/matryer/is v1.4.1 h1:55ehd8zaGABKLXQUe2awZ99BD/PTc2ls+KV/dXphgEQ=
3139
github.com/matryer/is v1.4.1/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU=
40+
github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 h1:jWpvCLoY8Z/e3VKvlsiIGKtc+UG6U5vzxaoagmhXfyg=
41+
github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0/go.mod h1:QUyp042oQthUoa9bqDv0ER0wrtXnBruoNd7aNjkbP+k=
3242
github.com/migueleliasweb/go-github-mock v0.0.23 h1:GOi9oX/+Seu9JQ19V8bPDLqDI7M9iEOjo3g8v1k6L2c=
3343
github.com/migueleliasweb/go-github-mock v0.0.23/go.mod h1:NsT8FGbkvIZQtDu38+295sZEX8snaUiiQgsGxi6GUxk=
3444
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
3545
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
46+
github.com/prometheus/client_golang v1.18.0 h1:HzFfmkOzH5Q8L8G+kSJKUx5dtG87sewO+FoDDqP5Tbk=
47+
github.com/prometheus/client_golang v1.18.0/go.mod h1:T+GXkCk5wSJyOqMIzVgvvjFDlkOQntgjkJWKrN5txjA=
48+
github.com/prometheus/client_model v0.5.0 h1:VQw1hfvPvk3Uv6Qf29VrPF32JB6rtbgI6cYPYQjL0Qw=
49+
github.com/prometheus/client_model v0.5.0/go.mod h1:dTiFglRmd66nLR9Pv9f0mZi7B7fk5Pm3gvsjB5tr+kI=
50+
github.com/prometheus/common v0.45.0 h1:2BGz0eBc2hdMDLnO/8n0jeB3oPrt2D08CekT0lneoxM=
51+
github.com/prometheus/common v0.45.0/go.mod h1:YJmSTw9BoKxJplESWWxlbyttQR4uaEcGyv9MZjVOJsY=
52+
github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo=
53+
github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo=
54+
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
55+
github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
3656
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
3757
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
3858
github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c=
@@ -56,6 +76,8 @@ golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
5676
golang.org/x/oauth2 v0.17.0 h1:6m3ZPmLEFdVxKKWnKq4VqZ60gutO35zm+zrAHVmHyDQ=
5777
golang.org/x/oauth2 v0.17.0/go.mod h1:OzPDGQiuQMguemayvdylqddI7qcD9lnSDb+1FiwQ5HA=
5878
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
79+
golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y=
80+
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
5981
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
6082
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
6183
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
@@ -70,10 +92,12 @@ google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp0
7092
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
7193
google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
7294
google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
73-
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
7495
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
75-
gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
96+
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
97+
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
7698
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
99+
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
100+
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
77101
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
78102
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
79103
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

pkg/core/core.go

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,11 @@
55

66
package core
77

8-
import "context"
8+
import (
9+
"context"
10+
11+
"github.com/prometheus/client_golang/prometheus"
12+
)
913

1014
type ModuleVersion struct {
1115
// Version is a SemVer version string that specifies the version for a module.
@@ -19,4 +23,5 @@ type ModuleVersion struct {
1923
type ModuleStore interface {
2024
ListModuleVersions(ctx context.Context, namespace, name, provider string) ([]*ModuleVersion, error)
2125
GetModuleVersion(ctx context.Context, namespace, name, provider, version string) (*ModuleVersion, error)
26+
Metrics() []prometheus.Collector
2227
}

pkg/registry/registry.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import (
1616
"github.com/go-chi/chi/v5"
1717
"github.com/go-chi/chi/v5/middleware"
1818
"github.com/nrkno/terraform-registry/pkg/core"
19+
"github.com/prometheus/client_golang/prometheus"
1920
"go.uber.org/zap"
2021
)
2122

@@ -152,6 +153,20 @@ func (reg *Registry) RequestLogger() func(next http.Handler) http.Handler {
152153

153154
// SPDX-SnippetEnd
154155

156+
// Metrics returns a Prometheus registry containing metrics for the module store.
157+
func (reg *Registry) Metrics() *prometheus.Registry {
158+
root := prometheus.NewRegistry()
159+
160+
if reg.moduleStore != nil {
161+
modules := prometheus.NewRegistry()
162+
modules.MustRegister(reg.moduleStore.Metrics()...)
163+
prometheus.WrapRegistererWithPrefix("store_module_", modules)
164+
root.MustRegister(modules)
165+
}
166+
167+
return root
168+
}
169+
155170
func (reg *Registry) ServeHTTP(w http.ResponseWriter, r *http.Request) {
156171
reg.router.ServeHTTP(w, r)
157172
}

pkg/store/github/github.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import (
1414
"github.com/google/go-github/v43/github"
1515
goversion "github.com/hashicorp/go-version"
1616
"github.com/nrkno/terraform-registry/pkg/core"
17+
"github.com/prometheus/client_golang/prometheus"
1718
"go.uber.org/zap"
1819
"golang.org/x/oauth2"
1920
)
@@ -200,3 +201,7 @@ func (s *GitHubStore) searchRepositories(ctx context.Context) ([]*github.Reposit
200201

201202
return allRepos, nil
202203
}
204+
205+
func (s *GitHubStore) Metrics() []prometheus.Collector {
206+
return nil
207+
}

pkg/store/memory/memory.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import (
1111
"sync"
1212

1313
"github.com/nrkno/terraform-registry/pkg/core"
14+
"github.com/prometheus/client_golang/prometheus"
1415
)
1516

1617
// MemoryStore is an in-memory store implementation without a backend.
@@ -72,3 +73,7 @@ func (s *MemoryStore) GetModuleVersion(ctx context.Context, namespace, name, pro
7273

7374
return nil, fmt.Errorf("version '%s' not found for module '%s'", version, key)
7475
}
76+
77+
func (s *MemoryStore) Metrics() []prometheus.Collector {
78+
return nil
79+
}

pkg/store/s3/s3.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import (
1313
"github.com/aws/aws-sdk-go/service/s3"
1414
"github.com/aws/aws-sdk-go/service/s3/s3iface"
1515
"github.com/nrkno/terraform-registry/pkg/core"
16+
"github.com/prometheus/client_golang/prometheus"
1617
"go.uber.org/zap"
1718
)
1819

@@ -144,3 +145,7 @@ func isValidModuleSourcePath(path string) bool {
144145
r := regexp.MustCompile("^" + addrRegExp + "/" + verRegExp)
145146
return r.MatchString(path)
146147
}
148+
149+
func (s *S3Store) Metrics() []prometheus.Collector {
150+
return nil
151+
}

0 commit comments

Comments
 (0)