Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Simple conversion of the format returned by the API #10019

Merged
merged 9 commits into from
Jan 30, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.next.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ https://github.com/elastic/beats/compare/v7.0.0-alpha2...master[Check the HEAD d
- Rename `process.exe` to `process.executable` in add_process_metadata to align with ECS. {pull}9949[9949]
- Import ECS change https://github.com/elastic/ecs/pull/308[ecs#308]:
leaf field `user.group` is now the `group` field set. {pull}10275[10275]
- Update the code of Central Management to align with the new returned format. {pull}10019[10019]
- Docker and Kubernetes labels/annotations will be "dedoted" by default. {pull}10338[10338]

*Auditbeat*
Expand Down
33 changes: 29 additions & 4 deletions x-pack/libbeat/management/api/configuration.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
package api

import (
"encoding/json"
"fmt"
"net/http"
"reflect"
Expand Down Expand Up @@ -50,16 +51,40 @@ func (c *ConfigBlock) ConfigWithMeta() (*reload.ConfigWithMeta, error) {
}, nil
}

type configResponse struct {
Type string
Raw map[string]interface{}
}

func (c *configResponse) UnmarshalJSON(b []byte) error {
ph marked this conversation as resolved.
Show resolved Hide resolved
var resp = struct {
Type string `json:"type"`
Raw map[string]interface{} `json:"config"`
}{}

if err := json.Unmarshal(b, &resp); err != nil {
return err
}

converter := selectConverter(resp.Type)
newMap, err := converter(resp.Raw)
if err != nil {
return err
}
*c = configResponse{
Type: resp.Type,
Raw: newMap,
}
return nil
}

// Configuration retrieves the list of configuration blocks from Kibana
func (c *Client) Configuration(accessToken string, beatUUID uuid.UUID, configOK bool) (ConfigBlocks, error) {
headers := http.Header{}
headers.Set("kbn-beats-access-token", accessToken)

resp := struct {
ConfigBlocks []*struct {
Type string `json:"type"`
Raw map[string]interface{} `json:"config"`
} `json:"configuration_blocks"`
ConfigBlocks []*configResponse `json:"configuration_blocks"`
}{}
url := fmt.Sprintf("/api/beats/agent/%s/configuration?validSetting=%t", beatUUID, configOK)
statusCode, err := c.request("GET", url, nil, headers, &resp)
Expand Down
2 changes: 1 addition & 1 deletion x-pack/libbeat/management/api/configuration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ func TestConfiguration(t *testing.T) {

assert.Equal(t, "false", r.URL.Query().Get("validSetting"))

fmt.Fprintf(w, `{"configuration_blocks":[{"type":"filebeat.modules","config":{"module":"apache2"}},{"type":"metricbeat.modules","config":{"module":"system","period":"10s"}}]}`)
fmt.Fprintf(w, `{"configuration_blocks":[{"type":"filebeat.modules","config":{"_sub_type":"apache2"}},{"type":"metricbeat.modules","config":{"_sub_type":"system","period":"10s"}}]}`)
}))
defer server.Close()

Expand Down
79 changes: 79 additions & 0 deletions x-pack/libbeat/management/api/convert.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
// or more contributor license agreements. Licensed under the Elastic License;
// you may not use this file except in compliance with the Elastic License.

package api

import (
"fmt"
"strings"
)

type converter func(map[string]interface{}) (map[string]interface{}, error)

var mapper = map[string]converter{
".inputs": noopConvert,
".modules": convertMultiple,
"output": convertSingle,
}

var errSubTypeNotFound = fmt.Errorf("'%s' key not found", subTypeKey)

var (
subTypeKey = "_sub_type"
moduleKey = "module"
)

func selectConverter(t string) converter {
for k, v := range mapper {
if strings.Index(t, k) > -1 {
return v
}
}
return noopConvert
}

func convertSingle(m map[string]interface{}) (map[string]interface{}, error) {
subType, err := extractSubType(m)
if err != nil {
return nil, err
}

delete(m, subTypeKey)
newMap := map[string]interface{}{subType: m}
return newMap, nil
}

func convertMultiple(m map[string]interface{}) (map[string]interface{}, error) {
subType, err := extractSubType(m)
if err != nil {
return nil, err
}

v, ok := m[moduleKey]

if ok && v != subType {
return nil, fmt.Errorf("module key already exist in the raw document and doesn't match the 'sub_type', expecting '%s' and received '%s", subType, v)
}

m[moduleKey] = subType
delete(m, subTypeKey)
return m, nil
}

func noopConvert(m map[string]interface{}) (map[string]interface{}, error) {
return m, nil
}

func extractSubType(m map[string]interface{}) (string, error) {
subType, ok := m[subTypeKey]
if !ok {
return "", errSubTypeNotFound
}

k, ok := subType.(string)
if !ok {
return "", fmt.Errorf("invalid type for `sub_type`, expecting a string received %T", subType)
}
return k, nil
}
111 changes: 111 additions & 0 deletions x-pack/libbeat/management/api/convert_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
// or more contributor license agreements. Licensed under the Elastic License;
// you may not use this file except in compliance with the Elastic License.

package api

import (
"reflect"
"testing"

"github.com/stretchr/testify/assert"
)

func TestConvertAPI(t *testing.T) {
tests := map[string]struct {
t string
config map[string]interface{}
expected map[string]interface{}
err bool
}{
"output": {
t: "output",
config: map[string]interface{}{
"_sub_type": "elasticsearch",
"username": "foobar",
},
expected: map[string]interface{}{
"elasticsearch": map[string]interface{}{
"username": "foobar",
},
},
},
"filebeat inputs": {
t: "filebeat.inputs",
config: map[string]interface{}{
"type": "log",
"paths": []string{
"/var/log/message.log",
"/var/log/system.log",
},
},
expected: map[string]interface{}{
"type": "log",
"paths": []string{
"/var/log/message.log",
"/var/log/system.log",
},
},
},
"filebeat modules": {
t: "filebeat.modules",
config: map[string]interface{}{
"_sub_type": "system",
},
expected: map[string]interface{}{
"module": "system",
},
},
"metricbeat modules": {
t: "metricbeat.modules",
config: map[string]interface{}{
"_sub_type": "logstash",
},
expected: map[string]interface{}{
"module": "logstash",
},
},
"badly formed output": {
err: true,
t: "output",
config: map[string]interface{}{
"nosubtype": "logstash",
},
},
"badly formed filebeat module": {
err: true,
t: "filebeat.modules",
config: map[string]interface{}{
"nosubtype": "logstash",
},
},
"badly formed metricbeat module": {
err: true,
t: "metricbeat.modules",
config: map[string]interface{}{
"nosubtype": "logstash",
},
},
"unknown type is passthrough": {
t: "unkown",
config: map[string]interface{}{
"nosubtype": "logstash",
},
expected: map[string]interface{}{
"nosubtype": "logstash",
},
},
}

for name, test := range tests {
test := test
t.Run(name, func(t *testing.T) {
converter := selectConverter(test.t)
newMap, err := converter(test.config)
if !assert.Equal(t, test.err, err != nil) {
return
}
assert.True(t, reflect.DeepEqual(newMap, test.expected))
})
}
}
69 changes: 69 additions & 0 deletions x-pack/libbeat/management/api/doc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
// or more contributor license agreements. Licensed under the Elastic License;
// you may not use this file except in compliance with the Elastic License.

/*
The Kibana CM Api returns a configuration format which cannot be ingested directly by our
configuration parser, it need to be transformed from the generic format into an adapted format
which is dependant on the type of configuration.


Translations:

Type: output

{
"configuration_blocks": [

{
"config": {
"_sub_type": "elasticsearch"
"_id": "12312341231231"
"hosts": [ "localhost" ],
"password": "foobar"
"username": "elastic"
},
"type": "output"
}
]
}

YAML representation:

{
"elasticsearch": {
"hosts": [ "localhost" ],
"password": "foobar"
"username": "elastic"
}
}


Type: *.modules

{
"configuration_blocks": [

{
"config": {
"_sub_type": "system"
"_id": "12312341231231"
"path" "foobar"
},
"type": "filebeat.module"
}
]
}

YAML representation:

[
{
"module": "system"
"path": "foobar"
}
]

*/

package api