Skip to content

Commit c7417c6

Browse files
authored
Add --delete-tag to otelcol-config (#1629)
This commit adds the delete-tag action to otelcol-config. The delete-tag action deletes a key out of the collector_fields object in conf.d settings. If the --override flag is provided, then the action deletes the tag out of conf.d overrides. If a user has manually added a tag to conf.d, and tries to delete it with this tool in override mode, then an error will be returned, and otelcol-config will refuse to proceed. Signed-off-by: Eric Chlebek <[email protected]>
1 parent d274c81 commit c7417c6

File tree

3 files changed

+241
-1
lines changed

3 files changed

+241
-1
lines changed
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
package main
2+
3+
import (
4+
"errors"
5+
"fmt"
6+
"sort"
7+
8+
"github.com/mikefarah/yq/v4/pkg/yqlib"
9+
)
10+
11+
// DeleteTagAction deletes a collector tag from conf.d.
12+
// If the --override flag is present, it will delete the tag from both the
13+
// overrides file and the settings file. If the --override flag is present and
14+
// the tag exists in a user-controlled file, an error will be returned.
15+
func DeleteTagAction(ctx *actionContext) error {
16+
conf, err := ReadConfigDir(ctx.ConfigDir)
17+
if err != nil {
18+
return err
19+
}
20+
21+
if conf.SumologicRemote != nil {
22+
return errors.New("delete-tag not supported for remote-controlled collectors")
23+
}
24+
25+
if ctx.Flags.Override {
26+
return deleteTagOverride(ctx, conf)
27+
} else {
28+
return deleteTag(ctx, conf, ConfDSettings, ctx.WriteConfD)
29+
}
30+
}
31+
32+
func deleteTagOverride(ctx *actionContext, conf ConfDir) error {
33+
readOrder := sort.StringSlice{}
34+
for key := range conf.ConfD {
35+
readOrder = append(readOrder, key)
36+
}
37+
sort.Sort(sort.Reverse(readOrder))
38+
39+
// Check if there are matching tags in user-controlled files. If there are,
40+
// refuse to continue on the grounds that we don't support edits to user
41+
// created files.
42+
eval := yqlib.NewStringEvaluator()
43+
encoder := yqlib.YamlFormat.EncoderFactory()
44+
decoder := yqlib.YamlFormat.DecoderFactory()
45+
for _, tagName := range ctx.Flags.DeleteTags {
46+
key := fmt.Sprintf(".extensions.sumologic.collector_fields.%s", tagName)
47+
for _, confKey := range readOrder {
48+
doc := string(conf.ConfD[confKey])
49+
result, err := eval.Evaluate(key, doc, encoder, decoder)
50+
if err != nil {
51+
return fmt.Errorf("error evaluating yq expression: %s", err)
52+
}
53+
if len(result) > 0 && result != nullResult {
54+
if confKey != ConfDSettings && confKey != ConfDOverrides {
55+
return fmt.Errorf("can't delete tag %s: user setting in %s cannot be overridden", tagName, confKey)
56+
}
57+
}
58+
}
59+
}
60+
61+
if err := deleteTag(ctx, conf, ConfDOverrides, ctx.WriteConfDOverrides); err != nil {
62+
return err
63+
}
64+
65+
return deleteTag(ctx, conf, ConfDSettings, ctx.WriteConfD)
66+
}
67+
68+
func deleteTag(ctx *actionContext, conf ConfDir, docName string, writeDoc func([]byte) (int, error)) error {
69+
encoder := yqlib.YamlFormat.EncoderFactory()
70+
decoder := yqlib.YamlFormat.DecoderFactory()
71+
eval := yqlib.NewStringEvaluator()
72+
doc := conf.ConfD[docName]
73+
74+
if len(doc) == 0 {
75+
// tag does not exist, nor any other config for that matter
76+
return nil
77+
}
78+
79+
const keyFmt = "del(.extensions.sumologic.collector_fields.%s)"
80+
81+
for _, tagName := range ctx.Flags.DeleteTags {
82+
expression := fmt.Sprintf(keyFmt, tagName)
83+
84+
result, err := eval.EvaluateAll(expression, string(doc), encoder, decoder)
85+
if err != nil {
86+
return fmt.Errorf("can't delete tag %s: %s", tagName, err)
87+
}
88+
89+
doc = []byte(result)
90+
}
91+
92+
if _, err := writeDoc(doc); err != nil {
93+
return fmt.Errorf("error writing %s: %s", docName, err)
94+
}
95+
96+
return nil
97+
}
Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
package main
2+
3+
import (
4+
"bytes"
5+
"io/fs"
6+
"path"
7+
"testing"
8+
"testing/fstest"
9+
)
10+
11+
func TestDeleteTagAction(t *testing.T) {
12+
tests := []struct {
13+
Name string
14+
Conf fs.FS
15+
Flags []string
16+
ExpConfDWrite []byte
17+
ExpConfDOverridesWrite []byte
18+
ExpErr bool
19+
}{
20+
{
21+
Name: "doc missing",
22+
Conf: fstest.MapFS{},
23+
Flags: []string{"--delete-tag", "foo"},
24+
},
25+
{
26+
Name: "doc missing, overrides present",
27+
Conf: fstest.MapFS{
28+
path.Join(ConfDotD, ConfDOverrides): &fstest.MapFile{
29+
Data: []byte("extensions:\n sumologic:\n collector_fields:\n foo: bar\n"),
30+
},
31+
},
32+
Flags: []string{"--delete-tag", "foo"},
33+
},
34+
{
35+
Name: "tag deleted",
36+
Conf: fstest.MapFS{
37+
path.Join(ConfDotD, ConfDSettings): &fstest.MapFile{
38+
Data: []byte("extensions:\n sumologic:\n collector_fields:\n foo: bar\n bar: baz\n"),
39+
},
40+
},
41+
Flags: []string{"--delete-tag", "foo"},
42+
ExpConfDWrite: []byte("extensions:\n sumologic:\n collector_fields:\n bar: baz\n"),
43+
},
44+
{
45+
Name: "delete override",
46+
Conf: fstest.MapFS{
47+
path.Join(ConfDotD, ConfDOverrides): &fstest.MapFile{
48+
Data: []byte("extensions:\n sumologic:\n collector_fields:\n foo: bar\n bar: baz\n"),
49+
},
50+
},
51+
Flags: []string{"--override", "--delete-tag", "foo"},
52+
ExpConfDOverridesWrite: []byte("extensions:\n sumologic:\n collector_fields:\n bar: baz\n"),
53+
},
54+
{
55+
Name: "delete settings with override",
56+
Conf: fstest.MapFS{
57+
path.Join(ConfDotD, ConfDSettings): &fstest.MapFile{
58+
Data: []byte("extensions:\n sumologic:\n collector_fields:\n foo: bar\n bar: baz\n"),
59+
},
60+
},
61+
Flags: []string{"--override", "--delete-tag", "foo"},
62+
ExpConfDWrite: []byte("extensions:\n sumologic:\n collector_fields:\n bar: baz\n"),
63+
},
64+
{
65+
Name: "delete both settings and overrides",
66+
Conf: fstest.MapFS{
67+
path.Join(ConfDotD, ConfDSettings): &fstest.MapFile{
68+
Data: []byte("extensions:\n sumologic:\n collector_fields:\n foo: bar\n bar: baz\n"),
69+
},
70+
path.Join(ConfDotD, ConfDOverrides): &fstest.MapFile{
71+
Data: []byte("extensions:\n sumologic:\n collector_fields:\n foo: bar\n bar: baz\n"),
72+
},
73+
},
74+
Flags: []string{"--override", "--delete-tag", "foo"},
75+
ExpConfDWrite: []byte("extensions:\n sumologic:\n collector_fields:\n bar: baz\n"),
76+
ExpConfDOverridesWrite: []byte("extensions:\n sumologic:\n collector_fields:\n bar: baz\n"),
77+
},
78+
{
79+
Name: "error when key exists in a user-controlled file and override is used",
80+
Conf: fstest.MapFS{
81+
path.Join(ConfDotD, ConfDSettings): &fstest.MapFile{
82+
Data: []byte("extensions:\n sumologic:\n collector_fields:\n foo: bar\n bar: baz\n"),
83+
},
84+
path.Join(ConfDotD, "user-settings.yaml"): &fstest.MapFile{
85+
Data: []byte("extensions:\n sumologic:\n collector_fields:\n foo: bar\n bar: baz\n"),
86+
},
87+
},
88+
Flags: []string{"--override", "--delete-tag", "foo"},
89+
ExpErr: true,
90+
},
91+
{
92+
Name: "no error when key exists in a user-controlled file and override is not used",
93+
Conf: fstest.MapFS{
94+
path.Join(ConfDotD, ConfDSettings): &fstest.MapFile{
95+
Data: []byte("extensions:\n sumologic:\n collector_fields:\n foo: bar\n bar: baz\n"),
96+
},
97+
path.Join(ConfDotD, "user-settings.yaml"): &fstest.MapFile{
98+
Data: []byte("extensions:\n sumologic:\n collector_fields:\n foo: bar\n bar: baz\n"),
99+
},
100+
},
101+
Flags: []string{"--delete-tag", "foo"},
102+
ExpConfDWrite: []byte("extensions:\n sumologic:\n collector_fields:\n bar: baz\n"),
103+
},
104+
}
105+
106+
for _, test := range tests {
107+
t.Run(test.Name, func(t *testing.T) {
108+
var settingsWriter, overridesWriter func([]byte) (int, error)
109+
110+
if test.ExpConfDWrite != nil {
111+
settingsWriter = newTestWriter(test.ExpConfDWrite).Write
112+
} else {
113+
settingsWriter = errWriter{}.Write
114+
}
115+
116+
if test.ExpConfDOverridesWrite != nil {
117+
overridesWriter = newTestWriter(test.ExpConfDOverridesWrite).Write
118+
} else {
119+
overridesWriter = errWriter{}.Write
120+
}
121+
122+
stdoutBuf := new(bytes.Buffer)
123+
stderrBuf := new(bytes.Buffer)
124+
125+
actionContext := makeTestActionContext(t,
126+
test.Conf,
127+
test.Flags,
128+
stdoutBuf,
129+
stderrBuf,
130+
settingsWriter,
131+
overridesWriter,
132+
)
133+
134+
err := DeleteTagAction(actionContext)
135+
if test.ExpErr && err == nil {
136+
t.Fatal("expected non-nil error")
137+
}
138+
if !test.ExpErr && err != nil {
139+
t.Fatal(err)
140+
}
141+
})
142+
}
143+
}

pkg/tools/otelcol-config/flag_actions.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ var flagActions = map[string]action{
1616
flagHelp: helpAction,
1717
flagConfigDir: nullAction,
1818
flagAddTag: AddTagAction,
19-
flagDeleteTag: notImplementedAction,
19+
flagDeleteTag: DeleteTagAction,
2020
flagSetInstallationToken: SetInstallationTokenAction,
2121
flagEnableHostmetrics: notImplementedAction,
2222
flagDisableHostmetrics: notImplementedAction,

0 commit comments

Comments
 (0)