Skip to content

Commit

Permalink
Cherry-pick #25516 to 7.x: Fix Insert when array is empty (#25543)
Browse files Browse the repository at this point in the history
Cherry-pick #25516 to 7.x: Fix Insert when array is empty  (#25543)
  • Loading branch information
michalpristas authored May 5, 2021
1 parent 741ca40 commit e7da829
Show file tree
Hide file tree
Showing 7 changed files with 331 additions and 36 deletions.
1 change: 1 addition & 0 deletions x-pack/elastic-agent/CHANGELOG.next.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@
- Delay the restart of application when a status report of failure is given {pull}25339[25339]
- Don't log when upgrade capability doesn't apply {pull}25386[25386]
- Fixed issue when unversioned home is set and invoked watcher failing with ENOENT {issue}25371[25371]
- Fixed Elastic Agent: expecting Dict and received *transpiler.Key for '0' {issue}24453[24453]

==== New features

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,7 @@ func (e *Controller) update() error {
}
err = transpiler.Insert(ast, renderedInputs, "inputs")
if err != nil {
return err
return errors.New(err, "inserting rendered inputs failed")
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"github.com/elastic/go-sysinfo/types"

"github.com/elastic/beats/v7/x-pack/elastic-agent/pkg/agent/application/info"
"github.com/elastic/beats/v7/x-pack/elastic-agent/pkg/agent/errors"
"github.com/elastic/beats/v7/x-pack/elastic-agent/pkg/agent/transpiler"
"github.com/elastic/beats/v7/x-pack/elastic-agent/pkg/config"
"github.com/elastic/beats/v7/x-pack/elastic-agent/pkg/core/logger"
Expand Down Expand Up @@ -39,7 +40,7 @@ func InjectFleet(cfg *config.Config, hostInfo types.HostInfo, agentInfo *info.Ag
// copy top-level agent.* into fleet.agent.* (this gets sent to Applications in this structure)
if agent, ok := transpiler.Lookup(ast, "agent"); ok {
if err := transpiler.Insert(ast, agent, "fleet"); err != nil {
return err
return errors.New(err, "inserting agent info failed")
}
}

Expand All @@ -56,7 +57,7 @@ func InjectFleet(cfg *config.Config, hostInfo types.HostInfo, agentInfo *info.Ag
if value, ok := key.Value().(*transpiler.StrVal); ok {
hosts := transpiler.NewList([]transpiler.Node{transpiler.NewStrVal(value.String())})
if err := transpiler.Insert(ast, hosts, "fleet.hosts"); err != nil {
return err
return errors.New(err, "inserting fleet hosts failed")
}
}
}
Expand All @@ -68,13 +69,13 @@ func InjectFleet(cfg *config.Config, hostInfo types.HostInfo, agentInfo *info.Ag
transpiler.NewKey("id", transpiler.NewStrVal(hostInfo.UniqueID)),
}))
if err := transpiler.Insert(ast, host, "fleet"); err != nil {
return err
return errors.New(err, "inserting list of hosts failed")
}

// inject fleet.* from local AST to the rootAST so its present when sending to Applications.
err = transpiler.Insert(rootAst, fleet.Value().(transpiler.Node), "fleet")
if err != nil {
return err
return errors.New(err, "inserting fleet info failed")
}
return nil
}
Expand Down
2 changes: 1 addition & 1 deletion x-pack/elastic-agent/pkg/agent/install/uninstall.go
Original file line number Diff line number Diff line change
Expand Up @@ -253,7 +253,7 @@ func applyDynamics(ctx context.Context, log *logger.Logger, cfg *config.Config)
}
err = transpiler.Insert(ast, renderedInputs, "inputs")
if err != nil {
return nil, err
return nil, errors.New("inserting rendered inputs failed", err)
}
}

Expand Down
31 changes: 20 additions & 11 deletions x-pack/elastic-agent/pkg/agent/transpiler/ast.go
Original file line number Diff line number Diff line change
Expand Up @@ -1027,23 +1027,32 @@ func Lookup(a *AST, selector Selector) (Node, bool) {
// accept a new node.
func Insert(a *AST, node Node, to Selector) error {
current := a.root

for _, part := range splitPath(to) {
n, ok := current.Find(part)
if !ok {
switch t := current.(type) {
case *Key:
d, ok := t.value.(*Dict)
if !ok {
return fmt.Errorf("expecting Dict and received %T for '%s'", t, part)
}
switch vt := t.value.(type) {
case *Dict:
newNode := &Key{name: part, value: &Dict{}}
vt.value = append(vt.value, newNode)

newNode := &Key{name: part, value: &Dict{}}
d.value = append(d.value, newNode)
vt.sort()

d.sort()
current = newNode
continue
case *List:
// inserting at index but array empty
newNode := &Dict{}
vt.value = append(vt.value, newNode)

current = newNode
continue
default:
return fmt.Errorf("expecting collection and received %T for '%s'", to, to)
}

current = newNode
continue
case *Dict:
newNode := &Key{name: part, value: &Dict{}}
t.value = append(t.value, newNode)
Expand All @@ -1053,7 +1062,7 @@ func Insert(a *AST, node Node, to Selector) error {
current = newNode
continue
default:
return fmt.Errorf("expecting Dict and received %T for '%s'", t, part)
return fmt.Errorf("expecting Dict and received %T for '%s'", t, to)
}
}

Expand All @@ -1064,7 +1073,7 @@ func Insert(a *AST, node Node, to Selector) error {
// that could exist after the selector.
d, ok := current.(*Key)
if !ok {
return fmt.Errorf("expecting Key and received %T", current)
return fmt.Errorf("expecting Key and received %T for '%s'", current, to)
}

switch nt := node.(type) {
Expand Down
178 changes: 178 additions & 0 deletions x-pack/elastic-agent/pkg/agent/transpiler/ast_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -375,6 +375,184 @@ func TestAST(t *testing.T) {
})
}

func TestInsert(t *testing.T) {
testcases := map[string]struct {
hashmap map[string]interface{}
selector Selector
node Node
expected *AST
}{
"insert root": {
selector: "inputs",
node: NewList([]Node{
NewDict([]Node{
NewKey("type", NewStrVal("test-key")),
}),
}),
hashmap: map[string]interface{}{
"outputs": map[string]interface{}{
"type": "elasticsearch",
"host": "demo.host.co",
},
},
expected: &AST{
root: &Dict{
value: []Node{
&Key{
name: "inputs",
value: NewList([]Node{
NewDict([]Node{
NewKey("type", NewStrVal("test-key")),
}),
}),
},
&Key{
name: "outputs",
value: NewDict(
[]Node{
&Key{name: "host", value: &StrVal{value: "demo.host.co"}},
&Key{name: "type", value: &StrVal{value: "elasticsearch"}},
}),
},
},
},
},
},
"insert sub key": {
selector: "outputs.sub",
node: NewList([]Node{
NewDict([]Node{
NewKey("type", NewStrVal("test-key")),
}),
}),
hashmap: map[string]interface{}{
"outputs": map[string]interface{}{
"type": "elasticsearch",
"host": "demo.host.co",
},
},
expected: &AST{
root: &Dict{
value: []Node{
&Key{
name: "outputs",
value: NewDict(
[]Node{
&Key{name: "host", value: &StrVal{value: "demo.host.co"}},
&Key{name: "sub", value: NewList([]Node{
NewDict([]Node{
NewKey("type", NewStrVal("test-key")),
}),
})},
&Key{name: "type", value: &StrVal{value: "elasticsearch"}},
}),
},
},
},
},
},
"insert at index": {
selector: "inputs.0.sub",
node: NewList([]Node{
NewDict([]Node{
NewKey("type", NewStrVal("test-key")),
}),
}),
hashmap: map[string]interface{}{
"inputs": []interface{}{
map[string]interface{}{
"type": "log/docker",
"ignore_older": "20s",
},
},
},
expected: &AST{
root: &Dict{
value: []Node{
&Key{
name: "inputs",
value: NewList(
[]Node{
NewDict([]Node{
NewKey("ignore_older", NewStrVal("20s")),
NewKey("sub", NewList([]Node{
NewDict([]Node{
NewKey("type", NewStrVal("test-key")),
}),
})),
NewKey("type", NewStrVal("log/docker")),
}),
}),
},
},
},
},
},

"insert at index when array empty": {
selector: "inputs.0.sub",
node: NewList([]Node{
NewDict([]Node{
NewKey("type", NewStrVal("test-key")),
}),
}),
hashmap: map[string]interface{}{
"inputs": make([]interface{}, 0),
"outputs": map[string]interface{}{
"type": "elasticsearch",
"host": "demo.host.co",
},
},
expected: &AST{
root: &Dict{
value: []Node{
&Key{
name: "inputs",
value: NewList(
[]Node{
NewDict(
[]Node{
NewKey("sub", NewList([]Node{
NewDict([]Node{
NewKey("type", NewStrVal("test-key")),
}),
})),
},
),
}),
},
&Key{
name: "outputs",
value: NewDict(
[]Node{
NewKey("host", &StrVal{value: "demo.host.co"}),
NewKey("type", &StrVal{value: "elasticsearch"}),
}),
},
},
},
},
},
}

for name, test := range testcases {
t.Run(name, func(t *testing.T) {
ast, err := NewAST(test.hashmap)
require.NoError(t, err)

err = Insert(ast, test.node, test.selector)
require.NoError(t, err)

if !assert.True(t, reflect.DeepEqual(test.expected, ast)) {
t.Logf(
`received: %+v
expected: %+v`, ast, test.expected)
}

})
}
}

func TestSelector(t *testing.T) {
testcases := map[string]struct {
hashmap map[string]interface{}
Expand Down
Loading

0 comments on commit e7da829

Please sign in to comment.