Skip to content

Commit e4c8fb6

Browse files
Add support for variables in outputs and default provider (#6602) (#6754)
Adds support for context variables (ONLY) in outputs. Adds support for a default provide prefix to be defined for variables (default is env). This provides support for the original ${ES_PASSWORD} when used in outputs to still work as it will automatically map that to ${env.ES_PASSWORD} and it will get resolved for the context variables the same as it was being done by go-ucfg. This includes an improvement to how variables are observed in the composable controller and how it is used by the coordinator. Now when a set of observable's are passed to the composable controller it will return the current set of variables after the debounce time, this ensures that before the variables are substituted that it is using the latest set of variables. Without this change running would always show an error at first with ${env.ES_PASSWORD} is an unknown variable and then less than a few milliseconds it would find it. This change removes that behavior and is able to find the variable on initial render. (cherry picked from commit b59d51a) Co-authored-by: Blake Rouse <[email protected]>
1 parent d80d79d commit e4c8fb6

31 files changed

+667
-146
lines changed
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
# Kind can be one of:
2+
# - breaking-change: a change to previously-documented behavior
3+
# - deprecation: functionality that is being removed in a later release
4+
# - bug-fix: fixes a problem in a previous version
5+
# - enhancement: extends functionality but does not break or fix existing behavior
6+
# - feature: new functionality
7+
# - known-issue: problems that we are aware of in a given version
8+
# - security: impacts on the security of a product or a user’s deployment.
9+
# - upgrade: important information for someone upgrading from a prior version
10+
# - other: does not fit into any of the other categories
11+
kind: feature
12+
13+
# Change summary; a 80ish characters long description of the change.
14+
summary: Add context variable support to outputs
15+
16+
# Long description; in case the summary is not enough to describe the change
17+
# this field accommodate a description without length limits.
18+
# NOTE: This field will be rendered only for breaking-change and known-issue kinds at the moment.
19+
description: |
20+
Adds support for using context variable providers in the outputs section of a policy. Includes fallback support
21+
to reference env provider when no provider prefix is provided in the variable.
22+
23+
# Affected component; usually one of "elastic-agent", "fleet-server", "filebeat", "metricbeat", "auditbeat", "all", etc.
24+
component: elastic-agent
25+
26+
# PR URL; optional; the PR number that added the changeset.
27+
# If not present is automatically filled by the tooling finding the PR where this changelog fragment has been added.
28+
# NOTE: the tooling supports backports, so it's able to fill the original PR number instead of the backport PR number.
29+
# Please provide it if you are adding a fragment for a different PR.
30+
pr: https://github.com/elastic/elastic-agent/pull/6602
31+
32+
# Issue URL; optional; the GitHub issue related to this changeset (either closes or is part of).
33+
# If not present is automatically filled by the tooling with the issue linked to the PR number.
34+
issue: https://github.com/elastic/elastic-agent/issues/6376

internal/pkg/agent/application/coordinator/coordinator.go

Lines changed: 46 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -172,8 +172,11 @@ type ConfigManager interface {
172172
type VarsManager interface {
173173
Runner
174174

175+
// DefaultProvider returns the default provider that the variable manager is configured to use.
176+
DefaultProvider() string
177+
175178
// Observe instructs the variables to observe.
176-
Observe([]string)
179+
Observe(context.Context, []string) ([]*transpiler.Vars, error)
177180

178181
// Watch returns the chanel to watch for variable changes.
179182
Watch() <-chan []*transpiler.Vars
@@ -1244,7 +1247,11 @@ func (c *Coordinator) processConfigAgent(ctx context.Context, cfg *config.Config
12441247
}
12451248

12461249
// pass the observed vars from the AST to the varsMgr
1247-
c.observeASTVars()
1250+
err = c.observeASTVars(ctx)
1251+
if err != nil {
1252+
// only possible error here is the context being cancelled
1253+
return err
1254+
}
12481255

12491256
// Disabled for 8.8.0 release in order to limit the surface
12501257
// https://github.com/elastic/security-team/issues/6501
@@ -1327,25 +1334,32 @@ func (c *Coordinator) generateAST(cfg *config.Config) (err error) {
13271334
// observeASTVars identifies the variables that are referenced in the computed AST and passed to
13281335
// the varsMgr so it knows what providers are being referenced. If a providers is not being
13291336
// referenced then the provider does not need to be running.
1330-
func (c *Coordinator) observeASTVars() {
1337+
func (c *Coordinator) observeASTVars(ctx context.Context) error {
13311338
if c.varsMgr == nil {
13321339
// No varsMgr (only happens in testing)
1333-
return
1340+
return nil
13341341
}
1335-
if c.ast == nil {
1336-
// No AST; no vars
1337-
c.varsMgr.Observe(nil)
1338-
return
1342+
var vars []string
1343+
if c.ast != nil {
1344+
inputs, ok := transpiler.Lookup(c.ast, "inputs")
1345+
if ok {
1346+
vars = inputs.Vars(vars, c.varsMgr.DefaultProvider())
1347+
}
1348+
outputs, ok := transpiler.Lookup(c.ast, "outputs")
1349+
if ok {
1350+
vars = outputs.Vars(vars, c.varsMgr.DefaultProvider())
1351+
}
13391352
}
1340-
inputs, ok := transpiler.Lookup(c.ast, "inputs")
1341-
if !ok {
1342-
// No inputs; no vars
1343-
c.varsMgr.Observe(nil)
1344-
return
1353+
updated, err := c.varsMgr.Observe(ctx, vars)
1354+
if err != nil {
1355+
// context cancel
1356+
return err
13451357
}
1346-
var vars []string
1347-
vars = inputs.Vars(vars)
1348-
c.varsMgr.Observe(vars)
1358+
if updated != nil {
1359+
// provided an updated set of vars (observed changed)
1360+
c.vars = updated
1361+
}
1362+
return nil
13491363
}
13501364

13511365
// processVars updates the transpiler vars in the Coordinator.
@@ -1421,6 +1435,8 @@ func (c *Coordinator) generateComponentModel() (err error) {
14211435
}()
14221436

14231437
ast := c.ast.ShallowClone()
1438+
1439+
// perform variable substitution for inputs
14241440
inputs, ok := transpiler.Lookup(ast, "inputs")
14251441
if ok {
14261442
renderedInputs, err := transpiler.RenderInputs(inputs, c.vars)
@@ -1433,6 +1449,20 @@ func (c *Coordinator) generateComponentModel() (err error) {
14331449
}
14341450
}
14351451

1452+
// perform variable substitution for outputs
1453+
// outputs only support the context variables (dynamic provides are not provide to the outputs)
1454+
outputs, ok := transpiler.Lookup(ast, "outputs")
1455+
if ok {
1456+
renderedOutputs, err := transpiler.RenderOutputs(outputs, c.vars)
1457+
if err != nil {
1458+
return fmt.Errorf("rendering outputs failed: %w", err)
1459+
}
1460+
err = transpiler.Insert(ast, renderedOutputs, "outputs")
1461+
if err != nil {
1462+
return fmt.Errorf("inserting rendered outputs failed: %w", err)
1463+
}
1464+
}
1465+
14361466
cfg, err := ast.Map()
14371467
if err != nil {
14381468
return fmt.Errorf("failed to convert ast to map[string]interface{}: %w", err)

internal/pkg/agent/application/coordinator/coordinator_test.go

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -928,7 +928,7 @@ func BenchmarkCoordinator_generateComponentModel(b *testing.B) {
928928
require.NoError(b, err)
929929
vars := make([]*transpiler.Vars, len(varsMaps))
930930
for i, vm := range varsMaps {
931-
vars[i], err = transpiler.NewVars(fmt.Sprintf("%d", i), vm, mapstr.M{})
931+
vars[i], err = transpiler.NewVars(fmt.Sprintf("%d", i), vm, mapstr.M{}, "")
932932
require.NoError(b, err)
933933
}
934934

@@ -1188,6 +1188,9 @@ func (l *configChange) Fail(err error) {
11881188
}
11891189

11901190
type fakeVarsManager struct {
1191+
varsMx sync.RWMutex
1192+
vars []*transpiler.Vars
1193+
11911194
varsCh chan []*transpiler.Vars
11921195
errCh chan error
11931196

@@ -1222,19 +1225,29 @@ func (f *fakeVarsManager) Watch() <-chan []*transpiler.Vars {
12221225
return f.varsCh
12231226
}
12241227

1225-
func (f *fakeVarsManager) Observe(observed []string) {
1228+
func (f *fakeVarsManager) Observe(ctx context.Context, observed []string) ([]*transpiler.Vars, error) {
12261229
f.observedMx.Lock()
12271230
defer f.observedMx.Unlock()
12281231
f.observed = observed
1232+
f.varsMx.RLock()
1233+
defer f.varsMx.RUnlock()
1234+
return f.vars, nil
12291235
}
12301236

12311237
func (f *fakeVarsManager) Vars(ctx context.Context, vars []*transpiler.Vars) {
1238+
f.varsMx.Lock()
1239+
f.vars = vars
1240+
f.varsMx.Unlock()
12321241
select {
12331242
case <-ctx.Done():
12341243
case f.varsCh <- vars:
12351244
}
12361245
}
12371246

1247+
func (f *fakeVarsManager) DefaultProvider() string {
1248+
return ""
1249+
}
1250+
12381251
type fakeOTelManager struct {
12391252
updateCallback func(*confmap.Conf) error
12401253
result error

internal/pkg/agent/application/coordinator/coordinator_unit_test.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1104,7 +1104,7 @@ inputs:
11041104
components = nil
11051105
vars, err := transpiler.NewVars("", map[string]interface{}{
11061106
"TEST_VAR": "input-id",
1107-
}, nil)
1107+
}, nil, "")
11081108
require.NoError(t, err, "Vars creation must succeed")
11091109
varsChan <- []*transpiler.Vars{vars}
11101110
coord.runLoopIteration(ctx)
@@ -1121,7 +1121,7 @@ inputs:
11211121
components = nil
11221122
vars, err = transpiler.NewVars("", map[string]interface{}{
11231123
"TEST_VAR": "changed-input-id",
1124-
}, nil)
1124+
}, nil, "")
11251125
require.NoError(t, err, "Vars creation must succeed")
11261126
varsChan <- []*transpiler.Vars{vars}
11271127
coord.runLoopIteration(ctx)
@@ -1239,7 +1239,7 @@ func TestCoordinatorInitiatesUpgrade(t *testing.T) {
12391239
// (Coordinator will only regenerate its component model when it has non-nil
12401240
// vars).
12411241
func emptyVars(t *testing.T) []*transpiler.Vars {
1242-
vars, err := transpiler.NewVars("", map[string]interface{}{}, nil)
1242+
vars, err := transpiler.NewVars("", map[string]interface{}{}, nil, "")
12431243
require.NoError(t, err, "Vars creation must succeed")
12441244
return []*transpiler.Vars{vars}
12451245
}

internal/pkg/agent/application/coordinator/diagnostics_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -198,7 +198,7 @@ func TestDiagnosticVariables(t *testing.T) {
198198
map[string]interface{}{
199199
"testvar": "testvalue",
200200
},
201-
nil)
201+
nil, "")
202202
require.NoError(t, err)
203203

204204
expected := `

internal/pkg/agent/transpiler/ast.go

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ type Node interface {
6464

6565
// Vars adds to the array with the variables identified in the node. Returns the array in-case
6666
// the capacity of the array had to be changed.
67-
Vars([]string) []string
67+
Vars([]string, string) []string
6868

6969
// Apply apply the current vars, returning the new value for the node. This does not modify the original Node.
7070
Apply(*Vars) (Node, error)
@@ -182,10 +182,10 @@ func (d *Dict) Hash64With(h *xxhash.Digest) error {
182182
}
183183

184184
// Vars returns a list of all variables referenced in the dictionary.
185-
func (d *Dict) Vars(vars []string) []string {
185+
func (d *Dict) Vars(vars []string, defaultProvider string) []string {
186186
for _, v := range d.value {
187187
k := v.(*Key)
188-
vars = k.Vars(vars)
188+
vars = k.Vars(vars, defaultProvider)
189189
}
190190
return vars
191191
}
@@ -318,11 +318,11 @@ func (k *Key) Hash64With(h *xxhash.Digest) error {
318318
}
319319

320320
// Vars returns a list of all variables referenced in the value.
321-
func (k *Key) Vars(vars []string) []string {
321+
func (k *Key) Vars(vars []string, defaultProvider string) []string {
322322
if k.value == nil {
323323
return vars
324324
}
325-
return k.value.Vars(vars)
325+
return k.value.Vars(vars, defaultProvider)
326326
}
327327

328328
// Apply applies the vars to the value. This does not modify the original node.
@@ -463,9 +463,9 @@ func (l *List) ShallowClone() Node {
463463
}
464464

465465
// Vars returns a list of all variables referenced in the list.
466-
func (l *List) Vars(vars []string) []string {
466+
func (l *List) Vars(vars []string, defaultProvider string) []string {
467467
for _, v := range l.value {
468-
vars = v.Vars(vars)
468+
vars = v.Vars(vars, defaultProvider)
469469
}
470470
return vars
471471
}
@@ -552,12 +552,12 @@ func (s *StrVal) Hash64With(h *xxhash.Digest) error {
552552
}
553553

554554
// Vars returns a list of all variables referenced in the string.
555-
func (s *StrVal) Vars(vars []string) []string {
555+
func (s *StrVal) Vars(vars []string, defaultProvider string) []string {
556556
// errors are ignored (if there is an error determine the vars it will also error computing the policy)
557557
_, _ = replaceVars(s.value, func(variable string) (Node, Processors, bool) {
558558
vars = append(vars, variable)
559559
return nil, nil, false
560-
}, false)
560+
}, false, defaultProvider)
561561
return vars
562562
}
563563

@@ -613,7 +613,7 @@ func (s *IntVal) ShallowClone() Node {
613613
}
614614

615615
// Vars does nothing. Cannot have variable in an IntVal.
616-
func (s *IntVal) Vars(vars []string) []string {
616+
func (s *IntVal) Vars(vars []string, defaultProvider string) []string {
617617
return vars
618618
}
619619

@@ -691,7 +691,7 @@ func (s *UIntVal) Hash64With(h *xxhash.Digest) error {
691691
}
692692

693693
// Vars does nothing. Cannot have variable in an UIntVal.
694-
func (s *UIntVal) Vars(vars []string) []string {
694+
func (s *UIntVal) Vars(vars []string, defaultProvider string) []string {
695695
return vars
696696
}
697697

@@ -764,7 +764,7 @@ func (s *FloatVal) hashString() string {
764764
}
765765

766766
// Vars does nothing. Cannot have variable in an FloatVal.
767-
func (s *FloatVal) Vars(vars []string) []string {
767+
func (s *FloatVal) Vars(vars []string, defaultProvider string) []string {
768768
return vars
769769
}
770770

@@ -843,7 +843,7 @@ func (s *BoolVal) Hash64With(h *xxhash.Digest) error {
843843
}
844844

845845
// Vars does nothing. Cannot have variable in an BoolVal.
846-
func (s *BoolVal) Vars(vars []string) []string {
846+
func (s *BoolVal) Vars(vars []string, defaultProvider string) []string {
847847
return vars
848848
}
849849

0 commit comments

Comments
 (0)