Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
aos
GitHub Repository: aos/grafana-agent
Path: blob/main/pkg/config/remote_config.go
4096 views
1
package config
2
3
import (
4
"fmt"
5
"io"
6
"net/http"
7
"net/url"
8
"time"
9
10
"github.com/grafana/agent/pkg/config/instrumentation"
11
"github.com/prometheus/common/config"
12
)
13
14
// supported remote config provider schemes
15
const (
16
httpScheme = "http"
17
httpsScheme = "https"
18
)
19
20
// remoteOpts struct contains agent remote config options
21
type remoteOpts struct {
22
url *url.URL
23
HTTPClientConfig *config.HTTPClientConfig
24
}
25
26
// remoteProvider interface should be implemented by config providers
27
type remoteProvider interface {
28
retrieve() ([]byte, error)
29
}
30
31
// newRemoteProvider constructs a new remote configuration provider. The rawURL is parsed
32
// and a provider is constructed based on the URL's scheme.
33
func newRemoteProvider(rawURL string, opts *remoteOpts) (remoteProvider, error) {
34
u, err := url.Parse(rawURL)
35
if err != nil {
36
return nil, fmt.Errorf("error parsing rawURL %s: %w", rawURL, err)
37
}
38
if opts == nil {
39
// Default provider opts
40
opts = &remoteOpts{}
41
}
42
opts.url = u
43
44
switch u.Scheme {
45
case "":
46
// if no scheme, assume local file path, return nil and let caller handle.
47
return nil, nil
48
case httpScheme, httpsScheme:
49
httpP, err := newHTTPProvider(opts)
50
if err != nil {
51
return nil, fmt.Errorf("error constructing httpProvider: %w", err)
52
}
53
return httpP, nil
54
default:
55
return nil, fmt.Errorf("remote config scheme not supported: %s", u.Scheme)
56
}
57
}
58
59
// Remote Config Providers
60
// httpProvider - http/https provider
61
type httpProvider struct {
62
myURL *url.URL
63
httpClient *http.Client
64
}
65
66
// newHTTPProvider constructs a new httpProvider
67
func newHTTPProvider(opts *remoteOpts) (*httpProvider, error) {
68
httpClientConfig := config.HTTPClientConfig{}
69
if opts.HTTPClientConfig != nil {
70
err := opts.HTTPClientConfig.Validate()
71
if err != nil {
72
return nil, err
73
}
74
httpClientConfig = *opts.HTTPClientConfig
75
}
76
httpClient, err := config.NewClientFromConfig(httpClientConfig, "remote-config")
77
if err != nil {
78
return nil, err
79
}
80
return &httpProvider{
81
myURL: opts.url,
82
httpClient: httpClient,
83
}, nil
84
}
85
86
type retryAfterError struct {
87
retryAfter time.Duration
88
}
89
90
func (r retryAfterError) Error() string {
91
return fmt.Sprintf("server indicated to retry after %s", r.retryAfter)
92
}
93
94
// retrieve implements remoteProvider and fetches the config
95
func (p httpProvider) retrieve() ([]byte, error) {
96
response, err := p.httpClient.Get(p.myURL.String())
97
if err != nil {
98
instrumentation.InstrumentRemoteConfigFetchError()
99
return nil, fmt.Errorf("request failed: %w", err)
100
}
101
defer response.Body.Close()
102
103
instrumentation.InstrumentRemoteConfigFetch(response.StatusCode)
104
105
if response.StatusCode == http.StatusTooManyRequests {
106
retryAfter := response.Header.Get("Retry-After")
107
if retryAfter == "" {
108
return nil, fmt.Errorf("server indicated to retry, but no Retry-After header was provided")
109
}
110
retryAfterDuration, err := time.ParseDuration(retryAfter)
111
if err != nil {
112
return nil, fmt.Errorf("server indicated to retry, but Retry-After header was not a valid duration: %w", err)
113
}
114
return nil, retryAfterError{retryAfter: retryAfterDuration}
115
}
116
117
if response.StatusCode/100 != 2 {
118
return nil, fmt.Errorf("error fetching config: status code: %d", response.StatusCode)
119
}
120
bb, err := io.ReadAll(response.Body)
121
if err != nil {
122
return nil, err
123
}
124
return bb, nil
125
}
126
127