Skip to content

Commit 16440f1

Browse files
yama6abwplotka
authored andcommitted
feat: add /api/v1/status/buildinfo endpoint for Grafana compatibility
1 parent 282666f commit 16440f1

File tree

6 files changed

+127
-1
lines changed

6 files changed

+127
-1
lines changed

cmd/frontend/main.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@ import (
3535
"time"
3636

3737
"github.com/GoogleCloudPlatform/prometheus-engine/cmd/frontend/internal/rule"
38+
"github.com/GoogleCloudPlatform/prometheus-engine/internal/promapi"
39+
"github.com/GoogleCloudPlatform/prometheus-engine/pkg/export"
3840
"github.com/GoogleCloudPlatform/prometheus-engine/pkg/ui"
3941
"github.com/go-kit/log"
4042
"github.com/go-kit/log/level"
@@ -130,6 +132,12 @@ func main() {
130132
ruleEndpointURLs = append(ruleEndpointURLs, *ruleEndpointURL)
131133
}
132134

135+
version, err := export.Version()
136+
if err != nil {
137+
_ = level.Error(logger).Log("msg", "Unable to fetch module version", "err", err)
138+
os.Exit(1)
139+
}
140+
133141
var g run.Group
134142
{
135143
term := make(chan os.Signal, 1)
@@ -172,8 +180,11 @@ func main() {
172180
)
173181

174182
server := &http.Server{Addr: *listenAddress}
183+
buildInfoHandler := http.HandlerFunc(promapi.BuildinfoHandlerFunc(log.With(logger, "component", "buildinfo-handler"), "frontend", version))
184+
http.Handle("/api/v1/status/buildinfo", buildInfoHandler)
175185
http.Handle("/metrics", promhttp.HandlerFor(metrics, promhttp.HandlerOpts{Registry: metrics}))
176186
http.Handle("/api/v1/rules", http.HandlerFunc(ruleProxy.RuleGroups))
187+
http.Handle("/api/v1/rules/", http.NotFoundHandler())
177188
http.Handle("/api/v1/alerts", http.HandlerFunc(ruleProxy.Alerts))
178189
http.Handle("/api/", authenticate(forward(logger, targetURL, transport)))
179190

cmd/rule-evaluator/Dockerfile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ COPY go.mod go.mod
1818
COPY go.sum go.sum
1919
COPY cmd cmd
2020
COPY pkg pkg
21+
COPY internal internal
2122
COPY vendor vendor
2223

2324
FROM buildbase as appbase

cmd/rule-evaluator/main.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ import (
3535

3636
"cloud.google.com/go/compute/metadata"
3737
"github.com/GoogleCloudPlatform/prometheus-engine/cmd/rule-evaluator/internal"
38+
"github.com/GoogleCloudPlatform/prometheus-engine/internal/promapi"
3839
"github.com/GoogleCloudPlatform/prometheus-engine/pkg/export"
3940
exportsetup "github.com/GoogleCloudPlatform/prometheus-engine/pkg/export/setup"
4041
"github.com/GoogleCloudPlatform/prometheus-engine/pkg/operator"
@@ -405,9 +406,16 @@ func main() {
405406
}
406407
})
407408

409+
// https://prometheus.io/docs/prometheus/latest/querying/api/#build-information
410+
buildInfoHandler := promapi.BuildinfoHandlerFunc(log.With(logger, "handler", "buildinfo"), "rule-evaluator", version)
411+
http.HandleFunc("/api/v1/status/buildinfo", buildInfoHandler)
412+
408413
// https://prometheus.io/docs/prometheus/latest/querying/api/#rules
409414
apiHandler := internal.NewAPI(logger, ruleEvaluator.rulesManager)
410415
http.HandleFunc("/api/v1/rules", apiHandler.HandleRulesEndpoint)
416+
http.HandleFunc("/api/v1/rules/", http.NotFound)
417+
418+
// https://prometheus.io/docs/prometheus/latest/querying/api/#alerts
411419
http.HandleFunc("/api/v1/alerts", apiHandler.HandleAlertsEndpoint)
412420

413421
g.Add(func() error {

internal/promapi/buildinfo.go

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
// Copyright 2024 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package promapi
16+
17+
import (
18+
"fmt"
19+
"net/http"
20+
"os"
21+
"runtime"
22+
"time"
23+
24+
"github.com/go-kit/log"
25+
"github.com/go-kit/log/level"
26+
promapiv1 "github.com/prometheus/prometheus/web/api/v1"
27+
)
28+
29+
const (
30+
timestampFormat = "20060102-15:04:05"
31+
buildinfoPath = "/api/v1/status/buildinfo"
32+
exposedVersion = "2.55.0" // We support APIs similar to the last pre 3.0 Prometheus version.
33+
)
34+
35+
// BuildinfoHandlerFunc simulates the /api/v1/status/buildinfo prometheus endpoint.
36+
// It is used by Grafana to determine the Prometheus flavor, e.g. to check whether the ruler-api is enabled.
37+
// binary: e.g. "frontend" or "rule-evaluator".
38+
func BuildinfoHandlerFunc(logger log.Logger, binaryName, binaryVersion string) func(http.ResponseWriter, *http.Request) {
39+
return func(w http.ResponseWriter, _ *http.Request) {
40+
// TODO(yama6a): Populate buildinfo at build time, analogous to: https://github.com/prometheus/common/blob/v0.60.0/version/info.go
41+
response := promapiv1.PrometheusVersion{
42+
Version: exposedVersion,
43+
Revision: fmt.Sprintf("gmp/%s-%s", binaryName, binaryVersion),
44+
Branch: "HEAD",
45+
BuildUser: "gmp@localhost",
46+
BuildDate: getBinaryCreatedTimestamp(logger),
47+
GoVersion: runtime.Version(),
48+
}
49+
WriteSuccessResponse(logger, w, http.StatusOK, buildinfoPath, response)
50+
}
51+
}
52+
53+
func getBinaryCreatedTimestamp(logger log.Logger) string {
54+
fileInfo, err := os.Stat(os.Args[0])
55+
if err != nil {
56+
_ = level.Error(logger).Log("msg", "Failed to get binary creation timestamp, usinng now()", "err", err)
57+
return time.Now().Format(timestampFormat)
58+
}
59+
60+
return fileInfo.ModTime().Format(timestampFormat)
61+
}

internal/promapi/buildinfo_test.go

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
// Copyright 2024 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package promapi
16+
17+
import (
18+
"encoding/json"
19+
"net/http"
20+
"net/http/httptest"
21+
"testing"
22+
23+
"github.com/go-kit/log"
24+
promapiv1 "github.com/prometheus/prometheus/web/api/v1"
25+
"github.com/stretchr/testify/require"
26+
)
27+
28+
func TestBuildinfoHandlerFunc(t *testing.T) {
29+
t.Parallel()
30+
31+
handleFunc := BuildinfoHandlerFunc(log.NewNopLogger(), "frontend", "v1.2.3")
32+
recorder := httptest.NewRecorder()
33+
req := httptest.NewRequest(http.MethodGet, "/api/v1/status/buildinfo", nil)
34+
35+
handleFunc(recorder, req)
36+
require.Equal(t, http.StatusOK, recorder.Code)
37+
38+
// unmarshal into promapiv1.PrometheusVersion object
39+
resp := Response[promapiv1.PrometheusVersion]{}
40+
require.NoError(t, json.Unmarshal(recorder.Body.Bytes(), &resp))
41+
defer recorder.Result().Body.Close()
42+
43+
require.Equal(t, exposedVersion, resp.Data.Version)
44+
require.Equal(t, "gmp/frontend-v1.2.3", resp.Data.Revision)
45+
}

internal/promapi/promapi.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ func writeResponse[T RulesResponseData | AlertsResponseData | GenericResponseDat
9292
}
9393

9494
// WriteSuccessResponse writes a successful Response to the given responseWriter w.
95-
func WriteSuccessResponse[T RulesResponseData | AlertsResponseData](logger log.Logger, w http.ResponseWriter, httpResponseCode int, endpointURI string, responseData T) {
95+
func WriteSuccessResponse[T RulesResponseData | AlertsResponseData | promapiv1.PrometheusVersion](logger log.Logger, w http.ResponseWriter, httpResponseCode int, endpointURI string, responseData T) {
9696
writeResponse(logger, w, httpResponseCode, endpointURI, Response[T]{
9797
Status: statusSuccess,
9898
Data: responseData,

0 commit comments

Comments
 (0)