-
Notifications
You must be signed in to change notification settings - Fork 32
/
Copy pathmanager.go
289 lines (229 loc) · 10.4 KB
/
manager.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
// Unless explicitly stated otherwise all files in this repository are licensed
// under the Apache License Version 2.0.
// This product includes software developed at Datadog (https://www.datadoghq.com/).
// Copyright 2025 Datadog, Inc.
package cloudservice
import (
"errors"
"fmt"
"io"
"net/http"
"strings"
"time"
"github.com/DataDog/chaos-controller/cloudservice/aws"
"github.com/DataDog/chaos-controller/cloudservice/datadog"
"github.com/DataDog/chaos-controller/cloudservice/gcp"
"github.com/DataDog/chaos-controller/cloudservice/types"
"go.uber.org/zap"
)
// CloudServicesProvidersManager represents an interface for managing cloud service providers and their IP ranges.
type CloudServicesProvidersManager interface {
// GetServiceList returns a list of service names provided by the specified cloud provider.
GetServiceList(cloudProviderName types.CloudProviderName) []string
// GetServicesIPRanges retrieves IP ranges for the specified services provided by the given cloud provider.
GetServicesIPRanges(cloudProviderName types.CloudProviderName, serviceNames []string) (map[string][]string, error)
// PullIPRanges triggers the manual pulling of IP ranges for all cloud providers.
PullIPRanges() error
// StartPeriodicPull starts the periodic process of pulling IP ranges from cloud providers.
StartPeriodicPull()
// StopPeriodicPull stops the periodic process of pulling IP ranges from cloud providers.
StopPeriodicPull()
// GetProviderByName retrieves the cloud services provider instance by its name.
GetProviderByName(name types.CloudProviderName) *CloudServicesProvider
}
type cloudServicesProvidersManager struct {
cloudProviders map[types.CloudProviderName]*CloudServicesProvider
log *zap.SugaredLogger
stopPeriodicPull chan bool
periodicPullInterval time.Duration
client *http.Client
}
// CloudServicesProvider Data and ip ranges manager of one cloud provider
type CloudServicesProvider struct {
// CloudProviderIPRangeManager is responsible for managing IP ranges for the cloud provider.
CloudProviderIPRangeManager CloudProviderIPRangeManager
// IPRangeInfo stores information about the IP ranges of the cloud services provided by the cloud provider.
IPRangeInfo *types.CloudProviderIPRangeInfo
// Conf contains the configuration settings for the cloud services provider.
Conf types.CloudProviderConfig
}
// CloudProviderIPRangeManager Methods to verify and transform a specifid ip ranges list from a provider
type CloudProviderIPRangeManager interface {
// IsNewVersion checks whether a given IP range data in the form of bytes is a new version compared to a given version string.
// It returns true if the data is a new version, otherwise false. An error is returned in case of any issues.
IsNewVersion(ipRangeData []byte, version string) (bool, error)
// ConvertToGenericIPRanges converts the given IP range data in the form of bytes to a generic CloudProviderIPRangeInfo structure.
// It returns the converted IP range information or an error in case of any issues during conversion.
ConvertToGenericIPRanges(ipRangeData []byte) (*types.CloudProviderIPRangeInfo, error)
}
// New creates a new instance of CloudServicesProvidersManager.
// It initializes the manager with cloud providers based on the configuration and sets up their IP range managers.
func New(log *zap.SugaredLogger, config types.CloudProviderConfigs, httpClientMock *http.Client) (CloudServicesProvidersManager, error) {
manager := &cloudServicesProvidersManager{
cloudProviders: map[types.CloudProviderName]*CloudServicesProvider{},
log: log,
periodicPullInterval: config.PullInterval,
}
if httpClientMock == nil {
manager.client = &http.Client{
Timeout: time.Second * 10,
}
} else {
manager.client = httpClientMock
}
// return an empty manager if all providers are disabled
if config.DisableAll {
log.Info("all cloud providers are disabled")
return manager, nil
}
for _, cp := range types.AllCloudProviders {
provider := &CloudServicesProvider{}
switch cp {
case types.CloudProviderAWS:
provider.CloudProviderIPRangeManager = aws.New()
provider.Conf.Enabled = config.AWS.Enabled
provider.Conf.IPRangesURL = config.AWS.IPRangesURL
case types.CloudProviderGCP:
provider.CloudProviderIPRangeManager = gcp.New()
provider.Conf.Enabled = config.GCP.Enabled
provider.Conf.IPRangesURL = config.GCP.IPRangesURL
provider.Conf.ExtraIPRanges = config.GCP.ExtraIPRanges
case types.CloudProviderDatadog:
provider.CloudProviderIPRangeManager = datadog.New()
provider.Conf.Enabled = config.Datadog.Enabled
provider.Conf.IPRangesURL = config.Datadog.IPRangesURL
}
if !provider.Conf.Enabled {
log.Debugw("a cloud provider was disabled", "provider", cp)
continue
}
manager.cloudProviders[cp] = provider
}
if err := manager.PullIPRanges(); err != nil {
manager.log.Error(err)
return nil, err
}
return manager, nil
}
// StartPeriodicPull go routine pulling every interval all ip ranges of all cloud providers set up.
func (s *cloudServicesProvidersManager) StartPeriodicPull() {
s.log.Infow("starting periodic pull and parsing of the cloud provider ip ranges", "interval", s.periodicPullInterval.String())
go func() {
for {
select {
case closed := <-s.stopPeriodicPull:
if closed {
return
}
case <-time.After(s.periodicPullInterval):
if err := s.PullIPRanges(); err != nil {
s.log.Errorw("an error occurred when pulling IP ranges", "error", err)
}
}
}
}()
}
// StopPeriodicPull stop the goroutine pulling all ip ranges of all cloud providers
func (s *cloudServicesProvidersManager) StopPeriodicPull() {
s.log.Infow("closing periodic pull and parsing of the cloud provider ip ranges")
s.stopPeriodicPull <- true
}
// PullIPRanges pull all ip ranges of all cloud providers
func (s *cloudServicesProvidersManager) PullIPRanges() error {
errorMessage := ""
s.log.Infow("pull and parse of the cloud provider ip ranges")
for cloudProviderName := range s.cloudProviders {
if err := s.pullIPRangesPerCloudProvider(cloudProviderName); err != nil {
errorMessage += fmt.Sprintf("could not get the new ip ranges from provider %s: %s\n", cloudProviderName, err.Error())
}
}
s.log.Infow("finished pull and parse of the cloud provider ip ranges")
if errorMessage != "" {
return errors.New(errorMessage)
}
return nil
}
// GetServicesIPRanges with a given list of service names and cloud provider name, returns the list of ip ranges of those services
func (s *cloudServicesProvidersManager) GetServicesIPRanges(cloudProviderName types.CloudProviderName, serviceNames []string) (map[string][]string, error) {
if s.cloudProviders[cloudProviderName] == nil {
return nil, fmt.Errorf("cloud provider %s is not configured or does not exist", cloudProviderName)
}
if len(serviceNames) == 0 {
return nil, fmt.Errorf("no cloud service list provided for cloud provider %s", cloudProviderName)
}
IPRangeInfo := s.cloudProviders[cloudProviderName].IPRangeInfo
IPRanges := map[string][]string{}
for _, serviceName := range serviceNames {
// if user has imputed the same service twice, we verify
if _, ok := IPRanges[serviceName]; ok {
continue
}
if _, ok := IPRangeInfo.IPRanges[serviceName]; !ok {
return nil, fmt.Errorf("service %s from %s does not exist, available services are: %s", serviceName, cloudProviderName, strings.Join(s.GetServiceList(cloudProviderName), ", "))
}
IPRanges[serviceName] = IPRangeInfo.IPRanges[serviceName]
}
return IPRanges, nil
}
// GetServiceList return the list of services of a specific cloud provider. Mostly used in disruption creation validation
func (s *cloudServicesProvidersManager) GetServiceList(cloudProviderName types.CloudProviderName) []string {
if s.cloudProviders[cloudProviderName] == nil || s.cloudProviders[cloudProviderName].IPRangeInfo == nil {
return nil
}
return s.cloudProviders[cloudProviderName].IPRangeInfo.ServiceList
}
// GetProviderByName retrieves a CloudServicesProvider instance by its name from the manager's collection of cloud providers.
func (s *cloudServicesProvidersManager) GetProviderByName(name types.CloudProviderName) *CloudServicesProvider {
return s.cloudProviders[name]
}
// pullIPRangesPerCloudProvider pull ip ranges of one cloud provider
func (s *cloudServicesProvidersManager) pullIPRangesPerCloudProvider(cloudProviderName types.CloudProviderName) error {
provider := s.cloudProviders[cloudProviderName]
if provider == nil {
return fmt.Errorf("cloud provider %s does not exist", cloudProviderName)
}
s.log.Debugw("pulling ip ranges from provider", "provider", cloudProviderName)
unparsedIPRange, err := s.requestIPRangesFromProvider(provider.Conf.IPRangesURL)
if err != nil {
return err
}
if provider.IPRangeInfo != nil {
isNewVersion, err := provider.CloudProviderIPRangeManager.IsNewVersion(unparsedIPRange, provider.IPRangeInfo.Version)
if err != nil {
return err
}
if !isNewVersion {
s.log.Debugw("no changes of ip ranges", "provider", cloudProviderName)
s.log.Debugw("finished pulling new version", "provider", cloudProviderName)
return nil
}
}
provider.IPRangeInfo, err = provider.CloudProviderIPRangeManager.ConvertToGenericIPRanges(unparsedIPRange)
for _, ipRangeList := range provider.Conf.ExtraIPRanges {
// Viper "normalizes" all map keys by casting them all to lower case: https://github.com/spf13/viper/issues/373
// Because the services for each cloud provider use different case methods, e.g., "Google" vs "S3" vs "synthetics",
// there's no easy way to undo this lowercasing. So we've stored the extra ranges in the following syntax:
// "service;iprange;iprange;...;iprange". We split by ';' once to find the service, then split by ';' again to find
// all extra ranges
serviceAndSplitIPRange := strings.SplitN(ipRangeList, ";", 2)
service := serviceAndSplitIPRange[0]
splitIPRange := strings.Split(serviceAndSplitIPRange[1], ";")
provider.IPRangeInfo.IPRanges[service] = append(provider.IPRangeInfo.IPRanges[service], splitIPRange...)
}
return err
}
// requestIPRangesFromProvider launches a HTTP GET request to pull the ip range json file from a url
func (s *cloudServicesProvidersManager) requestIPRangesFromProvider(url string) ([]byte, error) {
response, err := s.client.Get(url)
if err != nil {
return nil, err
}
body, err := io.ReadAll(response.Body)
if err != nil {
return nil, err
}
if err = response.Body.Close(); err != nil {
return nil, err
}
return body, nil
}