mirror of https://github.com/prometheus/prometheus
176 lines
4.9 KiB
Go
176 lines
4.9 KiB
Go
// Copyright 2018 The Prometheus Authors
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
package adapter
|
|
|
|
// NOTE: you do not need to edit this file when implementing a custom sd.
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"log/slog"
|
|
"os"
|
|
"path/filepath"
|
|
"reflect"
|
|
"sort"
|
|
|
|
"github.com/prometheus/client_golang/prometheus"
|
|
"github.com/prometheus/common/model"
|
|
|
|
"github.com/prometheus/prometheus/discovery"
|
|
"github.com/prometheus/prometheus/discovery/targetgroup"
|
|
)
|
|
|
|
type customSD struct {
|
|
Targets []string `json:"targets"`
|
|
Labels map[string]string `json:"labels"`
|
|
}
|
|
|
|
func fingerprint(group *targetgroup.Group) model.Fingerprint {
|
|
groupFingerprint := model.LabelSet{}.Fingerprint()
|
|
for _, targets := range group.Targets {
|
|
groupFingerprint ^= targets.Fingerprint()
|
|
}
|
|
groupFingerprint ^= group.Labels.Fingerprint()
|
|
return groupFingerprint
|
|
}
|
|
|
|
// Adapter runs an unknown service discovery implementation and converts its target groups
|
|
// to JSON and writes to a file for file_sd.
|
|
type Adapter struct {
|
|
ctx context.Context
|
|
disc discovery.Discoverer
|
|
groups map[string]*customSD
|
|
manager *discovery.Manager
|
|
output string
|
|
name string
|
|
logger *slog.Logger
|
|
}
|
|
|
|
func mapToArray(m map[string]*customSD) []customSD {
|
|
arr := make([]customSD, 0, len(m))
|
|
for _, v := range m {
|
|
arr = append(arr, *v)
|
|
}
|
|
return arr
|
|
}
|
|
|
|
func generateTargetGroups(allTargetGroups map[string][]*targetgroup.Group) map[string]*customSD {
|
|
groups := make(map[string]*customSD)
|
|
for k, sdTargetGroups := range allTargetGroups {
|
|
for _, group := range sdTargetGroups {
|
|
newTargets := make([]string, 0)
|
|
newLabels := make(map[string]string)
|
|
|
|
for _, targets := range group.Targets {
|
|
for _, target := range targets {
|
|
newTargets = append(newTargets, string(target))
|
|
}
|
|
}
|
|
sort.Strings(newTargets)
|
|
for name, value := range group.Labels {
|
|
newLabels[string(name)] = string(value)
|
|
}
|
|
|
|
sdGroup := customSD{
|
|
Targets: newTargets,
|
|
Labels: newLabels,
|
|
}
|
|
// Make a unique key, including group's fingerprint, in case the sd_type (map key) and group.Source is not unique.
|
|
groupFingerprint := fingerprint(group)
|
|
key := fmt.Sprintf("%s:%s:%s", k, group.Source, groupFingerprint.String())
|
|
groups[key] = &sdGroup
|
|
}
|
|
}
|
|
|
|
return groups
|
|
}
|
|
|
|
// Parses incoming target groups updates. If the update contains changes to the target groups
|
|
// Adapter already knows about, or new target groups, we Marshal to JSON and write to file.
|
|
func (a *Adapter) refreshTargetGroups(allTargetGroups map[string][]*targetgroup.Group) {
|
|
tempGroups := generateTargetGroups(allTargetGroups)
|
|
|
|
if !reflect.DeepEqual(a.groups, tempGroups) {
|
|
a.groups = tempGroups
|
|
err := a.writeOutput()
|
|
if err != nil {
|
|
a.logger.With("component", "sd-adapter").Error("failed to write output", "err", err)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Writes JSON formatted targets to output file.
|
|
func (a *Adapter) writeOutput() error {
|
|
arr := mapToArray(a.groups)
|
|
b, _ := json.MarshalIndent(arr, "", " ")
|
|
|
|
dir, _ := filepath.Split(a.output)
|
|
tmpfile, err := os.CreateTemp(dir, "sd-adapter")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer tmpfile.Close()
|
|
|
|
_, err = tmpfile.Write(b)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Close the file immediately for platforms (eg. Windows) that cannot move
|
|
// a file while a process is holding a file handle.
|
|
tmpfile.Close()
|
|
err = os.Rename(tmpfile.Name(), a.output)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (a *Adapter) runCustomSD(ctx context.Context) {
|
|
updates := a.manager.SyncCh()
|
|
for {
|
|
select {
|
|
case <-ctx.Done():
|
|
case allTargetGroups, ok := <-updates:
|
|
// Handle the case that a target provider exits and closes the channel
|
|
// before the context is done.
|
|
if !ok {
|
|
return
|
|
}
|
|
a.refreshTargetGroups(allTargetGroups)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Run starts a Discovery Manager and the custom service discovery implementation.
|
|
func (a *Adapter) Run() {
|
|
//nolint:errcheck
|
|
go a.manager.Run()
|
|
a.manager.StartCustomProvider(a.ctx, a.name, a.disc)
|
|
go a.runCustomSD(a.ctx)
|
|
}
|
|
|
|
// NewAdapter creates a new instance of Adapter.
|
|
func NewAdapter(ctx context.Context, file, name string, d discovery.Discoverer, logger *slog.Logger, sdMetrics map[string]discovery.DiscovererMetrics, registerer prometheus.Registerer) *Adapter {
|
|
return &Adapter{
|
|
ctx: ctx,
|
|
disc: d,
|
|
groups: make(map[string]*customSD),
|
|
manager: discovery.NewManager(ctx, logger, registerer, sdMetrics),
|
|
output: file,
|
|
name: name,
|
|
logger: logger,
|
|
}
|
|
}
|