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

Add initial support to validate the mappings in system tests #2214

Open
wants to merge 50 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
50 commits
Select commit Hold shift + click to select a range
f8c82cf
Add validation using mappings
mrodm Nov 7, 2024
e211e34
Remove validation per setting/parameter
mrodm Nov 7, 2024
724329a
Add environment variable
mrodm Nov 7, 2024
a2a4c92
[CI] Keep just one step to test system tests with stack agents
mrodm Nov 7, 2024
4ee6da6
[CI] Add steps to test new validation method
mrodm Nov 7, 2024
14d7e2a
Add tests
mrodm Nov 8, 2024
ff3d85d
Add exception for empty objects - TBC
mrodm Nov 8, 2024
b5bed3a
Update label CI step
mrodm Nov 8, 2024
2ef29cf
Reuse same fields validator to sanitize docs
mrodm Nov 8, 2024
36d59fe
Update field definition to type keyword
mrodm Nov 8, 2024
6c7af00
Update README test package nginx
mrodm Nov 8, 2024
4e12656
Add skip for dynamic objects
mrodm Nov 8, 2024
a326b7f
Add more checks about objects with dynamic true
mrodm Nov 11, 2024
8dedfc2
Add docs for environment variables
mrodm Nov 11, 2024
c36fc3b
Add more tests
mrodm Nov 11, 2024
a48f52d
Compare value constant_keyword just in one case
mrodm Nov 11, 2024
e94169c
Restore array type
mrodm Nov 11, 2024
ec0dd1c
Add validation for type array objects
mrodm Nov 11, 2024
7d7ad49
[CI] Remove leftover in label step
mrodm Nov 11, 2024
1949adf
Revamp comments and examples
mrodm Nov 12, 2024
ac612fd
Create private method for creating the mappings validator
mrodm Nov 12, 2024
facee62
Revamp comments
mrodm Nov 12, 2024
e03dbab
Include also the definition that contains multi_fields
mrodm Nov 12, 2024
1e05f97
Rename methods
mrodm Nov 13, 2024
20775d8
Fix typo in README
mrodm Nov 13, 2024
0f2f27d
Extract function to get the current path
mrodm Nov 13, 2024
6ccb514
Remove unnecessary line
mrodm Nov 19, 2024
322f605
Remove mappingDefinitions struct
mrodm Nov 19, 2024
0f4b634
Rename function to validate in schema
mrodm Nov 19, 2024
99fbe10
Extract function to validate constant_keyword type objects
mrodm Nov 19, 2024
f9db498
Add variable
mrodm Nov 19, 2024
21dca78
Update return statements
mrodm Nov 19, 2024
5dee363
Extract function validateObjectProperties
mrodm Nov 19, 2024
43df7d8
Set ES API client as mandatory
mrodm Nov 19, 2024
0b6f797
Move functions to elasticsearch package
mrodm Nov 20, 2024
5730b0e
Create a new struct for mappings validator
mrodm Nov 20, 2024
1f3b9f1
Updated check to validate if an object is fully dynamic
mrodm Nov 20, 2024
f7a594b
Add another test case for multi_fields
mrodm Nov 20, 2024
109cb07
Remove number of members condition in isObject
mrodm Nov 20, 2024
4f72eb2
Enable validation of each parameter of a mapping
mrodm Nov 20, 2024
b9078ad
Update transform used in comparison (diff)
mrodm Nov 21, 2024
a058dfb
Remove leftovers
mrodm Nov 21, 2024
1f0c180
Commented out one debug statement
mrodm Nov 21, 2024
06d5463
Refactor: Move to a new function
mrodm Nov 21, 2024
1faeab4
Updaste logger warn message
mrodm Nov 21, 2024
650b3ed
Add comments and rename functions
mrodm Nov 21, 2024
0f980fa
Add External ecs to all ECS fields imported
mrodm Nov 21, 2024
e20d563
Use just one schema to validate mappings not existing in preview
mrodm Nov 21, 2024
f4451d2
Add functions as MappingValidator methods
mrodm Nov 22, 2024
cdf7821
Skip nested empty mappings
mrodm Nov 22, 2024
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
94 changes: 42 additions & 52 deletions .buildkite/pipeline.trigger.integration.tests.sh
Original file line number Diff line number Diff line change
Expand Up @@ -44,14 +44,9 @@ CHECK_PACKAGES_TESTS=(
test-check-packages-benchmarks
test-check-packages-with-logstash
)
for independent_agent in false true ; do
for test in "${CHECK_PACKAGES_TESTS[@]}"; do
label_suffix=""
if [[ "$independent_agent" == "false" ]]; then
label_suffix=" (stack agent)"
fi
test_name=${test#"test-check-packages-"}
echo " - label: \":go: Integration test: ${test_name}${label_suffix}\""
echo " - label: \":go: Integration test: ${test_name}\""
echo " command: ./.buildkite/scripts/integration_tests.sh -t ${test}"
echo " agents:"
echo " provider: \"gcp\""
Expand All @@ -64,15 +59,10 @@ for test in "${CHECK_PACKAGES_TESTS[@]}"; do
if [[ $test =~ with-kind$ ]]; then
echo " - build/kubectl-dump.txt"
fi
if [[ "${independent_agent}" == "false" ]]; then
echo " env:"
echo " ELASTIC_PACKAGE_TEST_ENABLE_INDEPENDENT_AGENT: ${independent_agent}"
fi
done
done

pushd test/packages/false_positives > /dev/null
for package in $(find . -maxdepth 1 -mindepth 1 -type d) ; do
while IFS= read -r -d '' package ; do
package_name=$(basename "${package}")
echo " - label: \":go: Integration test (false positive): ${package_name}\""
echo " key: \"integration-false_positives-${package_name}\""
Expand All @@ -86,48 +76,56 @@ for package in $(find . -maxdepth 1 -mindepth 1 -type d) ; do
echo " - build/test-results/*.xml"
echo " - build/test-results/*.xml.expected-errors.txt" # these files are uploaded in case it is needed to review the xUnit files in case of CI reports success the step
echo " - build/test-coverage/coverage-*.xml" # these files should not be used to compute the final coverage of elastic-package
done
done < <(find . -maxdepth 1 -mindepth 1 -type d -print0)
popd > /dev/null

pushd test/packages/parallel > /dev/null
for independent_agent in false true; do
for package in $(find . -maxdepth 1 -mindepth 1 -type d) ; do
label_suffix=""
if [[ "$independent_agent" == "false" ]]; then
label_suffix=" (stack agent)"
fi
while IFS= read -r -d '' package ; do
package_name=$(basename "${package}")
if [[ "$independent_agent" == "false" && "$package_name" == "oracle" ]]; then
echoerr "Package \"${package_name}\" skipped: not supported with Elastic Agent running in the stack (missing required software)."
continue
fi

if [[ "$independent_agent" == "false" && "$package_name" == "auditd_manager" ]]; then
echoerr "Package \"${package_name}\" skipped: not supported with Elastic Agent running in the stack (missing capabilities)."
continue
fi
echo " - label: \":go: Integration test: ${package_name}\""
echo " key: \"integration-parallel-${package_name}-agent\""
echo " command: ./.buildkite/scripts/integration_tests.sh -t test-check-packages-parallel -p ${package_name}"
echo " env:"
echo " UPLOAD_SAFE_LOGS: 1"
echo " agents:"
echo " provider: \"gcp\""
echo " image: \"${UBUNTU_X86_64_AGENT_IMAGE}\""
echo " artifact_paths:"
echo " - build/test-results/*.xml"
echo " - build/test-coverage/coverage-*.xml" # these files should not be used to compute the final coverage of elastic-package
done < <(find . -maxdepth 1 -mindepth 1 -type d -print0)

if [[ "$independent_agent" == "false" && "$package_name" == "custom_entrypoint" ]]; then
echoerr "Package \"${package_name}\" skipped: not supported with Elastic Agent running in the stack (missing required files deployed in provisioning)."
continue
fi
# Run system tests with the Elastic Agent from the Elastic stack just for one package
package_name="apache"
echo " - label: \":go: Integration test: ${package_name} (stack agent)\""
echo " key: \"integration-parallel-${package_name}-stack-agent\""
echo " command: ./.buildkite/scripts/integration_tests.sh -t test-check-packages-parallel -p ${package_name}"
echo " env:"
echo " UPLOAD_SAFE_LOGS: 1"
echo " ELASTIC_PACKAGE_TEST_ENABLE_INDEPENDENT_AGENT: false"
echo " agents:"
echo " provider: \"gcp\""
echo " image: \"${UBUNTU_X86_64_AGENT_IMAGE}\""
echo " artifact_paths:"
echo " - build/test-results/*.xml"
echo " - build/test-coverage/coverage-*.xml" # these files should not be used to compute the final coverage of elastic-package

echo " - label: \":go: Integration test: ${package_name}${label_suffix}\""
echo " key: \"integration-parallel-${package_name}-agent-${independent_agent}\""
# Add steps to test validation method mappings
while IFS= read -r -d '' package ; do
package_name=$(basename "${package}")
echo " - label: \":go: Integration test: ${package_name} (just validate mappings)\""
echo " key: \"integration-parallel-${package_name}-agent-validate-mappings\""
echo " command: ./.buildkite/scripts/integration_tests.sh -t test-check-packages-parallel -p ${package_name}"
echo " env:"
echo " UPLOAD_SAFE_LOGS: 1"
if [[ "${independent_agent}" == "false" ]]; then
echo " ELASTIC_PACKAGE_TEST_ENABLE_INDEPENDENT_AGENT: ${independent_agent}"
fi
echo " ELASTIC_PACKAGE_FIELD_VALIDATION_TEST_METHOD: mappings"
echo " agents:"
echo " provider: \"gcp\""
echo " image: \"${UBUNTU_X86_64_AGENT_IMAGE}\""
echo " artifact_paths:"
echo " - build/test-results/*.xml"
echo " - build/test-coverage/coverage-*.xml" # these files should not be used to compute the final coverage of elastic-package
done
done
done < <(find . -maxdepth 1 -mindepth 1 -type d -print0)
popd > /dev/null

# TODO: Missing docker & docker-compose in MACOS ARM agent image, skip installation of packages in the meantime.
Expand Down Expand Up @@ -166,19 +164,11 @@ echo " image: \"${UBUNTU_X86_64_AGENT_IMAGE}\""
echo " artifact_paths:"
echo " - build/elastic-stack-dump/install-zip-shellinit/logs/*.log"

for independent_agent in false true; do
label_suffix=""
if [[ "$independent_agent" == "false" ]]; then
label_suffix=" (stack agent)"
fi
echo " - label: \":go: Integration test: system-flags${label_suffix}\""
echo " command: ./.buildkite/scripts/integration_tests.sh -t test-system-test-flags"
echo " agents:"
echo " provider: \"gcp\""
echo " image: \"${UBUNTU_X86_64_AGENT_IMAGE}\""
echo " env:"
echo " ELASTIC_PACKAGE_TEST_ENABLE_INDEPENDENT_AGENT: ${independent_agent}"
done
echo " - label: \":go: Integration test: system-flags\""
echo " command: ./.buildkite/scripts/integration_tests.sh -t test-system-test-flags"
echo " agents:"
echo " provider: \"gcp\""
echo " image: \"${UBUNTU_X86_64_AGENT_IMAGE}\""

echo " - label: \":go: Integration test: profiles-command\""
echo " command: ./.buildkite/scripts/integration_tests.sh -t test-profiles-command"
Expand Down
4 changes: 4 additions & 0 deletions .buildkite/scripts/integration_tests.sh
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,10 @@ if [[ "${TARGET}" == "${PARALLEL_TARGET}" ]] || [[ "${TARGET}" == "${FALSE_POSIT
package_folder="${package_folder}-stack_agent"
fi

if [[ "${ELASTIC_PACKAGE_FIELD_VALIDATION_TEST_METHOD:-""}" != "" ]]; then
package_folder="${package_folder}-${ELASTIC_PACKAGE_FIELD_VALIDATION_TEST_METHOD}"
fi

if [[ "${retry_count}" -ne 0 ]]; then
package_folder="${package_folder}_retry_${retry_count}"
fi
Expand Down
9 changes: 8 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -695,9 +695,16 @@ There are available some environment variables that could be used to change some
- `ELASTIC_PACKAGE_DISABLE_ELASTIC_AGENT_WOLFI`: If set to `true`, the Elastic Agent image used for running agents will be using the Ubuntu docker images
(e.g. `docker.elastic.co/elastic-agent/elastic-agent-complete`). If set to `false`, the Elastic Agent image used for the running agents will be based on the wolfi
images (e.g. `docker.elastic.co/elastic-agent/elastic-agent-wolfi`). Default: `false`.
- `ELASTIC_PACKAGE_TEST_DUMP_SCENARIO_DOCS. If the variable is set, elastic-package will dump to a file the documents generated
- `ELASTIC_PACKAGE_TEST_DUMP_SCENARIO_DOCS`. If the variable is set, elastic-package will dump to a file the documents generated
by system tests before they are verified. This is useful to know exactly what fields are being verified when investigating
issues on this step. Documents are dumped to a file in the system temporary directory. It is disabled by default.
- `ELASTIC_PACKAGE_TEST_ENABLE_INDEPENDENT_AGENT`. If the variable is set to false, all system tests defined in the package will use
the Elastic Agent started along with the stack. If set to true, a new Elastic Agent will be started and enrolled for each test defined in the
package (and unenrolled at the end of each test). Default: `true`.
- `ELASTIC_PACKAGE_FIELD_VALIDATION_TEST_METHOD`. This variable can take one of these values: `all`, `mappings` or `fields`. If this
variable is set to `fields`, then validation of fields will be based on the documents ingested into Elasticsearch. If this is set to
`mappings`, then validation of fields will be based on the mappings generated when the documents are ingested into Elasticsearch. If
set to `all`, then validation will be based on both methods mentioned previously. Default option: `fields`.

- To configure the Elastic stack to be used by `elastic-package`:
- `ELASTIC_PACKAGE_ELASTICSEARCH_HOST`: Host of the elasticsearch (e.g. https://127.0.0.1:9200)
Expand Down
82 changes: 82 additions & 0 deletions internal/elasticsearch/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -279,3 +279,85 @@ func (client *Client) redHealthCause(ctx context.Context) (string, error) {
}
return strings.Join(causes, ", "), nil
}

func (c *Client) SimulateIndexTemplate(ctx context.Context, indexTemplateName string) (json.RawMessage, json.RawMessage, error) {
resp, err := c.Indices.SimulateTemplate(
c.Indices.SimulateTemplate.WithContext(ctx),
c.Indices.SimulateTemplate.WithName(indexTemplateName),
)
if err != nil {
return nil, nil, fmt.Errorf("failed to get field mapping for data stream %q: %w", indexTemplateName, err)
}
defer resp.Body.Close()
if resp.IsError() {
return nil, nil, fmt.Errorf("error getting mapping: %s", resp)
}
body, err := io.ReadAll(resp.Body)
if err != nil {
return nil, nil, fmt.Errorf("error reading mapping body: %w", err)
}

type mappingsIndexTemplate struct {
DynamicTemplates json.RawMessage `json:"dynamic_templates"`
Properties json.RawMessage `json:"properties"`
}

type indexTemplateSimulated struct {
// Settings json.RawMessage `json:"settings"`
Mappings mappingsIndexTemplate `json:"mappings"`
}

type previewTemplate struct {
Template indexTemplateSimulated `json:"template"`
}

var preview previewTemplate

if err := json.Unmarshal(body, &preview); err != nil {
return nil, nil, fmt.Errorf("error unmarshaling mappings: %w", err)
}

return preview.Template.Mappings.DynamicTemplates, preview.Template.Mappings.Properties, nil
}

func (c *Client) DataStreamMappings(ctx context.Context, dataStreamName string) (json.RawMessage, json.RawMessage, error) {
mappingResp, err := c.Indices.GetMapping(
c.Indices.GetMapping.WithContext(ctx),
c.Indices.GetMapping.WithIndex(dataStreamName),
)
if err != nil {
return nil, nil, fmt.Errorf("failed to get field mapping for data stream %q: %w", dataStreamName, err)
}
defer mappingResp.Body.Close()
if mappingResp.IsError() {
return nil, nil, fmt.Errorf("error getting mapping: %s", mappingResp)
}
body, err := io.ReadAll(mappingResp.Body)
if err != nil {
return nil, nil, fmt.Errorf("error reading mapping body: %w", err)
}

type mappings struct {
DynamicTemplates json.RawMessage `json:"dynamic_templates"`
Properties json.RawMessage `json:"properties"`
}

mappingsRaw := map[string]struct {
Mappings mappings `json:"mappings"`
}{}

if err := json.Unmarshal(body, &mappingsRaw); err != nil {
return nil, nil, fmt.Errorf("error unmarshaling mappings: %w", err)
}

if len(mappingsRaw) != 1 {
return nil, nil, fmt.Errorf("exactly 1 mapping was expected, got %d", len(mappingsRaw))
}

var mappingsDefinition mappings
for _, v := range mappingsRaw {
mappingsDefinition = v.Mappings
}

return mappingsDefinition.DynamicTemplates, mappingsDefinition.Properties, nil
}
Loading