Skip to content

Commit 04152cd

Browse files
authored
chore: Use single source of truth for config (#483)
* chore: Use single source of truth for config Signed-off-by: Arthur Silva Sens <arthursens2005@gmail.com> * Use CLIFlag strings from config Signed-off-by: Arthur Silva Sens <arthursens2005@gmail.com> * Add License headers Signed-off-by: Arthur Silva Sens <arthursens2005@gmail.com> * Remove unused functions Signed-off-by: Arthur Silva Sens <arthursens2005@gmail.com> * Refactor config sharing strategy Signed-off-by: Arthur Silva Sens <arthursens2005@gmail.com> * Add tests to prevent config drift Signed-off-by: Arthur Silva Sens <arthursens2005@gmail.com> --------- Signed-off-by: Arthur Silva Sens <arthursens2005@gmail.com>
1 parent 637d306 commit 04152cd

File tree

10 files changed

+723
-268
lines changed

10 files changed

+723
-268
lines changed

config/config.go

Lines changed: 230 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,230 @@
1+
// Copyright The Prometheus Authors
2+
// Licensed under the Apache License, Version 2.0 (the "License");
3+
// you may not use this file except in compliance with the License.
4+
// You may obtain a copy of the License at
5+
//
6+
// http://www.apache.org/licenses/LICENSE-2.0
7+
//
8+
// Unless required by applicable law or agreed to in writing, software
9+
// distributed under the License is distributed on an "AS IS" BASIS,
10+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11+
// See the License for the specific language governing permissions and
12+
// limitations under the License.
13+
14+
package config
15+
16+
import (
17+
"context"
18+
"fmt"
19+
"net/http"
20+
"slices"
21+
"time"
22+
23+
"github.com/PuerkitoBio/rehttp"
24+
"github.com/prometheus-community/stackdriver_exporter/collectors"
25+
"github.com/prometheus-community/stackdriver_exporter/utils"
26+
"golang.org/x/oauth2/google"
27+
"google.golang.org/api/compute/v1"
28+
"google.golang.org/api/monitoring/v3"
29+
"google.golang.org/api/option"
30+
)
31+
32+
type Option struct {
33+
CLIFlag string
34+
OTelKey string
35+
Default any
36+
}
37+
38+
const (
39+
DefaultUniverseDomain = "googleapis.com"
40+
DefaultMaxRetries = 0
41+
DefaultHTTPTimeout = "10s"
42+
DefaultMaxBackoff = "5s"
43+
DefaultBackoffJitter = "1s"
44+
DefaultMetricsInterval = "5m"
45+
DefaultMetricsOffset = "0s"
46+
DefaultMetricsIngest = false
47+
DefaultFillMissing = true
48+
DefaultDropDelegated = false
49+
DefaultAggregateDeltas = false
50+
DefaultDeltasTTL = "30m"
51+
DefaultDescriptorTTL = "0s"
52+
DefaultDescriptorGoogleOnly = true
53+
)
54+
55+
// DefaultRetryStatuses must be treated as immutable after declaration.
56+
var DefaultRetryStatuses = []int{http.StatusServiceUnavailable}
57+
58+
var (
59+
ProjectIDs = Option{CLIFlag: "google.project-ids", OTelKey: "project_ids"}
60+
ProjectsFilter = Option{CLIFlag: "google.projects.filter", OTelKey: "projects_filter"}
61+
UniverseDomain = Option{CLIFlag: "google.universe-domain", OTelKey: "universe_domain", Default: DefaultUniverseDomain}
62+
MaxRetries = Option{CLIFlag: "stackdriver.max-retries", OTelKey: "max_retries", Default: DefaultMaxRetries}
63+
HTTPTimeout = Option{CLIFlag: "stackdriver.http-timeout", OTelKey: "http_timeout", Default: DefaultHTTPTimeout}
64+
MaxBackoff = Option{CLIFlag: "stackdriver.max-backoff", OTelKey: "max_backoff", Default: DefaultMaxBackoff}
65+
BackoffJitter = Option{CLIFlag: "stackdriver.backoff-jitter", OTelKey: "backoff_jitter", Default: DefaultBackoffJitter}
66+
RetryStatuses = Option{CLIFlag: "stackdriver.retry-statuses", OTelKey: "retry_statuses", Default: DefaultRetryStatuses}
67+
MetricsPrefixes = Option{CLIFlag: "monitoring.metrics-prefixes", OTelKey: "metrics_prefixes"}
68+
MetricsInterval = Option{CLIFlag: "monitoring.metrics-interval", OTelKey: "metrics_interval", Default: DefaultMetricsInterval}
69+
MetricsOffset = Option{CLIFlag: "monitoring.metrics-offset", OTelKey: "metrics_offset", Default: DefaultMetricsOffset}
70+
MetricsIngest = Option{CLIFlag: "monitoring.metrics-ingest-delay", OTelKey: "metrics_ingest_delay", Default: DefaultMetricsIngest}
71+
FillMissing = Option{CLIFlag: "collector.fill-missing-labels", OTelKey: "fill_missing_labels", Default: DefaultFillMissing}
72+
DropDelegated = Option{CLIFlag: "monitoring.drop-delegated-projects", OTelKey: "drop_delegated_projects", Default: DefaultDropDelegated}
73+
Filters = Option{CLIFlag: "monitoring.filters", OTelKey: "filters"}
74+
AggregateDeltas = Option{CLIFlag: "monitoring.aggregate-deltas", OTelKey: "aggregate_deltas", Default: DefaultAggregateDeltas}
75+
DeltasTTL = Option{CLIFlag: "monitoring.aggregate-deltas-ttl", OTelKey: "aggregate_deltas_ttl", Default: DefaultDeltasTTL}
76+
DescriptorTTL = Option{CLIFlag: "monitoring.descriptor-cache-ttl", OTelKey: "descriptor_cache_ttl", Default: DefaultDescriptorTTL}
77+
DescriptorGoogleOnly = Option{CLIFlag: "monitoring.descriptor-cache-only-google", OTelKey: "descriptor_cache_only_google", Default: DefaultDescriptorGoogleOnly}
78+
79+
AllOptions = []Option{
80+
ProjectIDs,
81+
ProjectsFilter,
82+
UniverseDomain,
83+
MaxRetries,
84+
HTTPTimeout,
85+
MaxBackoff,
86+
BackoffJitter,
87+
RetryStatuses,
88+
MetricsPrefixes,
89+
MetricsInterval,
90+
MetricsOffset,
91+
MetricsIngest,
92+
FillMissing,
93+
DropDelegated,
94+
Filters,
95+
AggregateDeltas,
96+
DeltasTTL,
97+
DescriptorTTL,
98+
DescriptorGoogleOnly,
99+
}
100+
)
101+
102+
type RuntimeConfig struct {
103+
ProjectIDs []string
104+
ProjectsFilter string
105+
UniverseDomain string
106+
MaxRetries int
107+
HTTPTimeout time.Duration
108+
MaxBackoff time.Duration
109+
BackoffJitter time.Duration
110+
RetryStatuses []int
111+
MetricsPrefixes []string
112+
MetricsInterval time.Duration
113+
MetricsOffset time.Duration
114+
MetricsIngest bool
115+
FillMissing bool
116+
DropDelegated bool
117+
Filters []string
118+
AggregateDeltas bool
119+
DeltasTTL time.Duration
120+
DescriptorTTL time.Duration
121+
DescriptorGoogleOnly bool
122+
}
123+
124+
func OTelComponentDefaults() map[string]interface{} {
125+
defaults := make(map[string]interface{}, len(AllOptions))
126+
for _, option := range AllOptions {
127+
if option.Default == nil {
128+
continue
129+
}
130+
// Option defaults are shared values and must not be mutated by callers.
131+
defaults[option.OTelKey] = option.Default
132+
}
133+
return defaults
134+
}
135+
136+
func ParseDuration(name, raw string) (time.Duration, error) {
137+
duration, err := time.ParseDuration(raw)
138+
if err != nil {
139+
return 0, fmt.Errorf("%s: invalid duration %q: %w", name, raw, err)
140+
}
141+
return duration, nil
142+
}
143+
144+
func ValidateRetryStatuses(codes []int) error {
145+
for _, code := range codes {
146+
if code < http.StatusContinue || code > 599 {
147+
return fmt.Errorf("retry status %d is not a valid HTTP status code", code)
148+
}
149+
}
150+
return nil
151+
}
152+
153+
func ParseMetricPrefixes(prefixes []string) []string {
154+
return utils.ParseMetricTypePrefixes(prefixes)
155+
}
156+
157+
func ParseMetricFilters(filters []string) []collectors.MetricFilter {
158+
return collectors.ParseMetricExtraFilters(filters)
159+
}
160+
161+
func DeduplicateProjectIDs(projectIDs []string) []string {
162+
normalized := slices.Clone(projectIDs)
163+
slices.Sort(normalized)
164+
return slices.Compact(normalized)
165+
}
166+
167+
func (c RuntimeConfig) MonitoringCollectorOptions() collectors.MonitoringCollectorOptions {
168+
return c.MonitoringCollectorOptionsForPrefixes(ParseMetricPrefixes(c.MetricsPrefixes))
169+
}
170+
171+
func (c RuntimeConfig) MonitoringCollectorOptionsForPrefixes(metricPrefixes []string) collectors.MonitoringCollectorOptions {
172+
return collectors.MonitoringCollectorOptions{
173+
MetricTypePrefixes: metricPrefixes,
174+
ExtraFilters: ParseMetricFilters(c.Filters),
175+
RequestInterval: c.MetricsInterval,
176+
RequestOffset: c.MetricsOffset,
177+
IngestDelay: c.MetricsIngest,
178+
FillMissingLabels: c.FillMissing,
179+
DropDelegatedProjects: c.DropDelegated,
180+
AggregateDeltas: c.AggregateDeltas,
181+
DescriptorCacheTTL: c.DescriptorTTL,
182+
DescriptorCacheOnlyGoogle: c.DescriptorGoogleOnly,
183+
}
184+
}
185+
186+
func (c RuntimeConfig) CollectorCacheTTL() time.Duration {
187+
if c.AggregateDeltas || c.DescriptorTTL > 0 {
188+
ttl := c.DeltasTTL
189+
if c.DescriptorTTL > ttl {
190+
ttl = c.DescriptorTTL
191+
}
192+
return ttl
193+
}
194+
195+
return 2 * time.Hour
196+
}
197+
198+
func DiscoverDefaultProjectID(ctx context.Context) (string, error) {
199+
credentials, err := google.FindDefaultCredentials(ctx, compute.ComputeScope)
200+
if err != nil {
201+
return "", err
202+
}
203+
if credentials.ProjectID == "" {
204+
return "", fmt.Errorf("unable to identify default GCP project")
205+
}
206+
return credentials.ProjectID, nil
207+
}
208+
209+
func (c RuntimeConfig) CreateMonitoringService(ctx context.Context) (*monitoring.Service, error) {
210+
googleClient, err := google.DefaultClient(ctx, monitoring.MonitoringReadScope)
211+
if err != nil {
212+
return nil, fmt.Errorf("error creating Google client: %w", err)
213+
}
214+
215+
googleClient.Timeout = c.HTTPTimeout
216+
googleClient.Transport = rehttp.NewTransport(
217+
googleClient.Transport,
218+
rehttp.RetryAll(
219+
rehttp.RetryMaxRetries(c.MaxRetries),
220+
rehttp.RetryStatuses(c.RetryStatuses...),
221+
),
222+
rehttp.ExpJitterDelay(c.BackoffJitter, c.MaxBackoff),
223+
)
224+
225+
service, err := monitoring.NewService(ctx, option.WithHTTPClient(googleClient), option.WithUniverseDomain(c.UniverseDomain))
226+
if err != nil {
227+
return nil, fmt.Errorf("error creating Google Stackdriver Monitoring service: %w", err)
228+
}
229+
return service, nil
230+
}

0 commit comments

Comments
 (0)