-
Notifications
You must be signed in to change notification settings - Fork 1.2k
/
Copy pathimport.go
331 lines (285 loc) · 10.6 KB
/
import.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
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
// 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 2016-present Datadog, Inc.
// Package common provides a set of common symbols needed by different packages,
// to avoid circular dependencies.
package common
import (
"fmt"
"os"
"os/user"
"path/filepath"
"strconv"
"strings"
"github.com/fatih/color"
yaml "gopkg.in/yaml.v2"
"github.com/DataDog/datadog-agent/pkg/config/legacy"
pkgconfigsetup "github.com/DataDog/datadog-agent/pkg/config/setup"
)
// TransformationFunc type represents transformation applicable to byte slices
type TransformationFunc func(rawData []byte) ([]byte, error)
// ImportConfig imports the agent5 configuration into the agent6 yaml config
func ImportConfig(oldConfigDir string, newConfigDir string, force bool) error {
datadogConfPath := filepath.Join(oldConfigDir, "datadog.conf")
datadogYamlPath := filepath.Join(newConfigDir, "datadog.yaml")
traceAgentConfPath := filepath.Join(newConfigDir, "trace-agent.conf")
configConverter := legacy.NewConfigConverter()
const cfgExt = ".yaml"
const dirExt = ".d"
// read the old configuration in memory
agentConfig, err := legacy.GetAgentConfig(datadogConfPath)
if err != nil {
return fmt.Errorf("unable to read data from %s: %v", datadogConfPath, err)
}
// the new config file might not exist, create it
created := false
if _, err := os.Stat(datadogYamlPath); os.IsNotExist(err) {
f, err := os.Create(datadogYamlPath)
if err != nil {
return fmt.Errorf("error creating %s: %v", datadogYamlPath, err)
}
f.Close()
created = true
}
// setup the configuration system
pkgconfigsetup.Datadog().AddConfigPath(newConfigDir)
_, err = pkgconfigsetup.LoadWithoutSecret(pkgconfigsetup.Datadog(), nil)
if err != nil {
return fmt.Errorf("unable to load Datadog config file: %s", err)
}
// we won't overwrite the conf file if it contains a valid api_key
if pkgconfigsetup.Datadog().GetString("api_key") != "" && !force {
return fmt.Errorf("%s seems to contain a valid configuration, run the command again with --force or -f to overwrite it",
datadogYamlPath)
}
// merge current agent configuration with the converted data
err = legacy.FromAgentConfig(agentConfig, configConverter)
if err != nil {
return fmt.Errorf("unable to convert configuration data from %s: %v", datadogConfPath, err)
}
// move existing config files to the new configuration directory
files, err := os.ReadDir(filepath.Join(oldConfigDir, "conf.d"))
if err != nil {
if os.IsNotExist(err) {
fmt.Fprintf(color.Output,
"%s does not exist, no config files to import.\n",
color.BlueString(filepath.Join(oldConfigDir, "conf.d")),
)
} else {
return fmt.Errorf("unable to list config files from %s: %v", oldConfigDir, err)
}
}
tr := []TransformationFunc{relocateMinCollectionInterval}
for _, f := range files {
if f.IsDir() || filepath.Ext(f.Name()) != cfgExt {
continue
}
checkName := strings.TrimSuffix(f.Name(), cfgExt)
src := filepath.Join(oldConfigDir, "conf.d", f.Name())
dst := filepath.Join(newConfigDir, "conf.d", checkName+dirExt, "conf"+cfgExt)
if f.Name() == "docker_daemon.yaml" {
err := legacy.ImportDockerConf(src, filepath.Join(newConfigDir, "conf.d", "docker.d", "conf.yaml"), force, configConverter)
if err != nil {
return err
}
continue
} else if f.Name() == "docker.yaml" {
// if people upgrade from a very old version of the agent who ship the old docker check.
fmt.Fprintf(
color.Output,
"Ignoring %s, old docker check has been deprecated.\n", color.YellowString(src),
)
continue
} else if f.Name() == "kubernetes.yaml" {
err := legacy.ImportKubernetesConf(src, filepath.Join(newConfigDir, "conf.d", "kubelet.d", "conf.yaml"), force, configConverter)
if err != nil {
return err
}
continue
}
if err := copyFile(src, dst, force, tr); err != nil {
return fmt.Errorf("unable to copy %s to %s: %v", src, dst, err)
}
fmt.Fprintf(
color.Output,
"Copied %s over the new %s directory\n",
color.BlueString("conf.d/"+f.Name()),
color.BlueString(checkName+dirExt),
)
}
// backup the original datadog.yaml to datadog.yaml.bak
if !created {
err = os.Rename(datadogYamlPath, datadogYamlPath+".bak")
if err != nil {
return fmt.Errorf("unable to create a backup for the existing file: %s", datadogYamlPath)
}
}
// marshal the config object to YAML
b, err := yaml.Marshal(pkgconfigsetup.Datadog().AllSettings())
if err != nil {
return fmt.Errorf("unable to marshal config to YAML: %v", err)
}
// dump the current configuration to datadog.yaml
// file permissions will be used only to create the file if doesn't exist,
// please note on Windows such permissions have no effect.
if err = os.WriteFile(datadogYamlPath, b, 0640); err != nil {
return fmt.Errorf("unable to write config to %s: %v", datadogYamlPath, err)
}
fmt.Fprintf(
color.Output,
"%s imported the contents of %s into %s\n",
color.GreenString("Success:"),
datadogConfPath,
datadogYamlPath,
)
// move existing config templates to the new auto_conf directory
autoConfFiles, err := os.ReadDir(filepath.Join(oldConfigDir, "conf.d", "auto_conf"))
if err != nil {
if os.IsNotExist(err) {
fmt.Fprintf(color.Output,
"%s does not exist, no auto_conf files to import.\n",
color.BlueString(filepath.Join(oldConfigDir, "conf.d", "auto_conf")),
)
} else {
return fmt.Errorf("unable to list auto_conf files from %s: %v", oldConfigDir, err)
}
}
for _, f := range autoConfFiles {
if f.IsDir() || filepath.Ext(f.Name()) != cfgExt {
continue
}
checkName := strings.TrimSuffix(f.Name(), cfgExt)
src := filepath.Join(oldConfigDir, "conf.d", "auto_conf", f.Name())
dst := filepath.Join(newConfigDir, "conf.d", checkName+dirExt, "auto_conf"+cfgExt)
if err := copyFile(src, dst, force, tr); err != nil {
fmt.Fprintf(os.Stderr, "unable to copy %s to %s: %v\n", src, dst, err)
continue
}
// Transform if needed AD configuration
input, err := os.ReadFile(dst)
if err != nil {
fmt.Fprintf(os.Stderr, "unable to open %s", dst)
continue
}
output := strings.Replace(string(input), "docker_images:", "ad_identifiers:", 1)
err = os.WriteFile(dst, []byte(output), 0640)
if err != nil {
fmt.Fprintf(os.Stderr, "unable to write %s", dst)
continue
}
fmt.Fprintf(
color.Output,
"Copied %s over the new %s directory\n",
color.BlueString("auto_conf/"+f.Name()),
color.BlueString(checkName+dirExt),
)
}
// Extract trace-agent specific info and dump it to its own config file.
imported, err := configTraceAgent(datadogConfPath, traceAgentConfPath, force)
if err != nil {
return fmt.Errorf("failed to import Trace Agent specific settings: %v", err)
}
if imported {
fmt.Printf("Wrote Trace Agent specific settings to %s\n", traceAgentConfPath)
}
return nil
}
// Copy the src file to dst. File attributes won't be copied. Apply all TransformationFunc while copying.
func copyFile(src, dst string, overwrite bool, transformations []TransformationFunc) error {
// if the file exists check whether we can overwrite
if _, err := os.Stat(dst); !os.IsNotExist(err) {
if overwrite {
// we'll overwrite, backup the original file first
err = os.Rename(dst, dst+".bak")
if err != nil {
return fmt.Errorf("unable to create a backup copy of the destination file: %v", err)
}
} else {
return fmt.Errorf("destination file already exists, run the command again with --force or -f to overwrite it")
}
}
// Create necessary destination directories
err := os.MkdirAll(filepath.Dir(dst), 0750)
if err != nil {
return err
}
data, err := os.ReadFile(src)
if err != nil {
return fmt.Errorf("unable to read file %s : %s", src, err)
}
for _, transformation := range transformations {
data, err = transformation(data)
if err != nil {
return fmt.Errorf("unable to convert file %s : %s", src, err)
}
}
os.WriteFile(dst, data, 0640) //nolint:errcheck
ddGroup, errGroup := user.LookupGroup("dd-agent")
ddUser, errUser := user.LookupId("dd-agent")
// Only change the owner/group of the configuration files if we can detect the dd-agent user
// This will not take affect on Windows/MacOS as the user is not available.
if errGroup == nil && errUser == nil {
ddGID, err := strconv.Atoi(ddGroup.Gid)
if err != nil {
return fmt.Errorf("Couldn't convert dd-agent group ID: %s into an int: %s", ddGroup.Gid, err)
}
ddUID, err := strconv.Atoi(ddUser.Uid)
if err != nil {
return fmt.Errorf("Couldn't convert dd-agent user ID: %s into an int: %s", ddUser.Uid, err)
}
err = os.Chown(dst, ddUID, ddGID)
if err != nil {
return fmt.Errorf("Couldn't change the file permissions for this check. Error: %s", err)
}
}
err = os.Chmod(dst, 0640)
if err != nil {
return err
}
return nil
}
// configTraceAgent extracts trace-agent specific info and dump to its own config file
func configTraceAgent(datadogConfPath, traceAgentConfPath string, overwrite bool) (bool, error) {
// if the file exists check whether we can overwrite
if _, err := os.Stat(traceAgentConfPath); !os.IsNotExist(err) {
if overwrite {
// we'll overwrite, backup the original file first
err = os.Rename(traceAgentConfPath, traceAgentConfPath+".bak")
if err != nil {
return false, fmt.Errorf("unable to create a backup for the existing file: %s", traceAgentConfPath)
}
} else {
return false, fmt.Errorf("destination file %s already exists, run the command again with --force or -f to overwrite it", traceAgentConfPath)
}
}
return legacy.ImportTraceAgentConfig(datadogConfPath, traceAgentConfPath)
}
func relocateMinCollectionInterval(rawData []byte) ([]byte, error) {
data := make(map[interface{}]interface{})
if err := yaml.Unmarshal(rawData, &data); err != nil {
return nil, fmt.Errorf("error while unmarshalling Yaml : %v", err)
}
if _, ok := data["init_config"]; ok {
if initConfig, ok := data["init_config"].(map[interface{}]interface{}); ok {
if _, ok := initConfig["min_collection_interval"]; ok {
if minCollectionInterval, ok := initConfig["min_collection_interval"].(int); ok {
delete(initConfig, "min_collection_interval")
insertMinCollectionInterval(data, minCollectionInterval)
}
}
}
}
return yaml.Marshal(data)
}
func insertMinCollectionInterval(rawData map[interface{}]interface{}, interval int) {
if _, ok := rawData["instances"]; ok {
if instances, ok := rawData["instances"].([]interface{}); ok {
for _, rawInstance := range instances {
if instance, ok := rawInstance.(map[interface{}]interface{}); ok {
instance["min_collection_interval"] = interval
}
}
}
}
}