From 314e40fba30aebb0e1c4d6ef25350a64f318ffc0 Mon Sep 17 00:00:00 2001 From: Brian Seeders Date: Fri, 20 Nov 2020 14:32:53 -0500 Subject: [PATCH] [CI] Initial TeamCity implementation (#81043) --- .ci/teamcity/bootstrap.sh | 21 +++ .ci/teamcity/checks/bundle_limits.sh | 7 + .ci/teamcity/checks/doc_api_changes.sh | 7 + .ci/teamcity/checks/file_casing.sh | 7 + .ci/teamcity/checks/i18n.sh | 7 + .ci/teamcity/checks/licenses.sh | 7 + .ci/teamcity/checks/telemetry.sh | 7 + .ci/teamcity/checks/test_hardening.sh | 7 + .ci/teamcity/checks/ts_projects.sh | 7 + .ci/teamcity/checks/type_check.sh | 7 + .../checks/verify_dependency_versions.sh | 7 + .ci/teamcity/checks/verify_notice.sh | 7 + .ci/teamcity/ci_stats.js | 59 ++++++ .ci/teamcity/ci_stats_complete.js | 18 ++ .ci/teamcity/default/accessibility.sh | 16 ++ .ci/teamcity/default/build.sh | 31 ++++ .ci/teamcity/default/build_plugins.sh | 20 ++ .ci/teamcity/default/ci_group.sh | 17 ++ .ci/teamcity/default/firefox.sh | 18 ++ .../default/saved_object_field_metrics.sh | 16 ++ .ci/teamcity/default/security_solution.sh | 16 ++ .ci/teamcity/es_snapshots/build.sh | 45 +++++ .ci/teamcity/es_snapshots/create_manifest.js | 82 +++++++++ .ci/teamcity/es_snapshots/promote_manifest.js | 53 ++++++ .ci/teamcity/oss/accessibility.sh | 14 ++ .ci/teamcity/oss/build.sh | 24 +++ .ci/teamcity/oss/build_plugins.sh | 16 ++ .ci/teamcity/oss/ci_group.sh | 15 ++ .ci/teamcity/oss/firefox.sh | 15 ++ .ci/teamcity/oss/plugin_functional.sh | 18 ++ .ci/teamcity/setup_ci_stats.js | 33 ++++ .ci/teamcity/setup_env.sh | 53 ++++++ .ci/teamcity/setup_node.sh | 42 +++++ .ci/teamcity/tests/mocha.sh | 7 + .ci/teamcity/tests/test_hardening.sh | 7 + .ci/teamcity/tests/test_projects.sh | 7 + .../tests/xpack_list_cyclic_dependency.sh | 8 + .../tests/xpack_siem_cyclic_dependency.sh | 8 + .ci/teamcity/util.sh | 81 +++++++++ .github/CODEOWNERS | 2 + .teamcity/.editorconfig | 4 + .teamcity/Kibana.png | Bin 0 -> 129139 bytes .teamcity/README.md | 156 ++++++++++++++++ .teamcity/pom.xml | 128 +++++++++++++ .teamcity/settings.kts | 12 ++ .teamcity/src/Extensions.kt | 169 +++++++++++++++++ .teamcity/src/builds/BaselineCi.kt | 38 ++++ .teamcity/src/builds/Checks.kt | 37 ++++ .teamcity/src/builds/FullCi.kt | 30 +++ .teamcity/src/builds/HourlyCi.kt | 34 ++++ .teamcity/src/builds/Lint.kt | 33 ++++ .teamcity/src/builds/PullRequestCi.kt | 78 ++++++++ .../builds/default/DefaultAccessibility.kt | 12 ++ .teamcity/src/builds/default/DefaultBuild.kt | 56 ++++++ .../src/builds/default/DefaultCiGroup.kt | 15 ++ .../src/builds/default/DefaultCiGroups.kt | 15 ++ .../src/builds/default/DefaultFirefox.kt | 12 ++ .../builds/default/DefaultFunctionalBase.kt | 19 ++ .../default/DefaultSavedObjectFieldMetrics.kt | 28 +++ .../builds/default/DefaultSecuritySolution.kt | 15 ++ .teamcity/src/builds/es_snapshots/Build.kt | 84 +++++++++ .teamcity/src/builds/es_snapshots/Promote.kt | 87 +++++++++ .../builds/es_snapshots/PromoteImmediate.kt | 79 ++++++++ .teamcity/src/builds/es_snapshots/Verify.kt | 96 ++++++++++ .teamcity/src/builds/oss/OssAccessibility.kt | 13 ++ .teamcity/src/builds/oss/OssBuild.kt | 41 +++++ .teamcity/src/builds/oss/OssCiGroup.kt | 15 ++ .teamcity/src/builds/oss/OssCiGroups.kt | 15 ++ .teamcity/src/builds/oss/OssFirefox.kt | 12 ++ .teamcity/src/builds/oss/OssFunctionalBase.kt | 18 ++ .../src/builds/oss/OssPluginFunctional.kt | 29 +++ .teamcity/src/builds/test/AllTests.kt | 12 ++ .../src/builds/test/ApiServerIntegration.kt | 17 ++ .teamcity/src/builds/test/Jest.kt | 19 ++ .teamcity/src/builds/test/JestIntegration.kt | 16 ++ .teamcity/src/builds/test/QuickTests.kt | 29 +++ .teamcity/src/builds/test/XPackJest.kt | 22 +++ .teamcity/src/projects/EsSnapshots.kt | 55 ++++++ .teamcity/src/projects/Kibana.kt | 171 ++++++++++++++++++ .teamcity/src/templates/DefaultTemplate.kt | 25 +++ .teamcity/src/templates/KibanaTemplate.kt | 141 +++++++++++++++ .teamcity/src/vcs/Elasticsearch.kt | 11 ++ .teamcity/src/vcs/Kibana.kt | 11 ++ .teamcity/tests/projects/KibanaTest.kt | 27 +++ .../report_failure.test.ts | 6 +- .../failed_tests_reporter/report_failure.ts | 8 +- .../run_failed_tests_reporter_cli.ts | 26 ++- .../__tests__/junit_report_generation.js | 1 + .../src/mocha/junit_report_generation.js | 1 + src/dev/precommit_hook/casing_check_config.js | 3 + .../security_solution/feature_controls.ts | 9 +- 91 files changed, 2813 insertions(+), 16 deletions(-) create mode 100755 .ci/teamcity/bootstrap.sh create mode 100755 .ci/teamcity/checks/bundle_limits.sh create mode 100755 .ci/teamcity/checks/doc_api_changes.sh create mode 100755 .ci/teamcity/checks/file_casing.sh create mode 100755 .ci/teamcity/checks/i18n.sh create mode 100755 .ci/teamcity/checks/licenses.sh create mode 100755 .ci/teamcity/checks/telemetry.sh create mode 100755 .ci/teamcity/checks/test_hardening.sh create mode 100755 .ci/teamcity/checks/ts_projects.sh create mode 100755 .ci/teamcity/checks/type_check.sh create mode 100755 .ci/teamcity/checks/verify_dependency_versions.sh create mode 100755 .ci/teamcity/checks/verify_notice.sh create mode 100644 .ci/teamcity/ci_stats.js create mode 100644 .ci/teamcity/ci_stats_complete.js create mode 100755 .ci/teamcity/default/accessibility.sh create mode 100755 .ci/teamcity/default/build.sh create mode 100755 .ci/teamcity/default/build_plugins.sh create mode 100755 .ci/teamcity/default/ci_group.sh create mode 100755 .ci/teamcity/default/firefox.sh create mode 100755 .ci/teamcity/default/saved_object_field_metrics.sh create mode 100755 .ci/teamcity/default/security_solution.sh create mode 100755 .ci/teamcity/es_snapshots/build.sh create mode 100644 .ci/teamcity/es_snapshots/create_manifest.js create mode 100644 .ci/teamcity/es_snapshots/promote_manifest.js create mode 100755 .ci/teamcity/oss/accessibility.sh create mode 100755 .ci/teamcity/oss/build.sh create mode 100755 .ci/teamcity/oss/build_plugins.sh create mode 100755 .ci/teamcity/oss/ci_group.sh create mode 100755 .ci/teamcity/oss/firefox.sh create mode 100755 .ci/teamcity/oss/plugin_functional.sh create mode 100644 .ci/teamcity/setup_ci_stats.js create mode 100755 .ci/teamcity/setup_env.sh create mode 100755 .ci/teamcity/setup_node.sh create mode 100755 .ci/teamcity/tests/mocha.sh create mode 100755 .ci/teamcity/tests/test_hardening.sh create mode 100755 .ci/teamcity/tests/test_projects.sh create mode 100755 .ci/teamcity/tests/xpack_list_cyclic_dependency.sh create mode 100755 .ci/teamcity/tests/xpack_siem_cyclic_dependency.sh create mode 100755 .ci/teamcity/util.sh create mode 100644 .teamcity/.editorconfig create mode 100644 .teamcity/Kibana.png create mode 100644 .teamcity/README.md create mode 100644 .teamcity/pom.xml create mode 100644 .teamcity/settings.kts create mode 100644 .teamcity/src/Extensions.kt create mode 100644 .teamcity/src/builds/BaselineCi.kt create mode 100644 .teamcity/src/builds/Checks.kt create mode 100644 .teamcity/src/builds/FullCi.kt create mode 100644 .teamcity/src/builds/HourlyCi.kt create mode 100644 .teamcity/src/builds/Lint.kt create mode 100644 .teamcity/src/builds/PullRequestCi.kt create mode 100755 .teamcity/src/builds/default/DefaultAccessibility.kt create mode 100644 .teamcity/src/builds/default/DefaultBuild.kt create mode 100755 .teamcity/src/builds/default/DefaultCiGroup.kt create mode 100644 .teamcity/src/builds/default/DefaultCiGroups.kt create mode 100755 .teamcity/src/builds/default/DefaultFirefox.kt create mode 100644 .teamcity/src/builds/default/DefaultFunctionalBase.kt create mode 100644 .teamcity/src/builds/default/DefaultSavedObjectFieldMetrics.kt create mode 100755 .teamcity/src/builds/default/DefaultSecuritySolution.kt create mode 100644 .teamcity/src/builds/es_snapshots/Build.kt create mode 100644 .teamcity/src/builds/es_snapshots/Promote.kt create mode 100644 .teamcity/src/builds/es_snapshots/PromoteImmediate.kt create mode 100644 .teamcity/src/builds/es_snapshots/Verify.kt create mode 100644 .teamcity/src/builds/oss/OssAccessibility.kt create mode 100644 .teamcity/src/builds/oss/OssBuild.kt create mode 100644 .teamcity/src/builds/oss/OssCiGroup.kt create mode 100644 .teamcity/src/builds/oss/OssCiGroups.kt create mode 100644 .teamcity/src/builds/oss/OssFirefox.kt create mode 100644 .teamcity/src/builds/oss/OssFunctionalBase.kt create mode 100644 .teamcity/src/builds/oss/OssPluginFunctional.kt create mode 100644 .teamcity/src/builds/test/AllTests.kt create mode 100644 .teamcity/src/builds/test/ApiServerIntegration.kt create mode 100644 .teamcity/src/builds/test/Jest.kt create mode 100644 .teamcity/src/builds/test/JestIntegration.kt create mode 100644 .teamcity/src/builds/test/QuickTests.kt create mode 100644 .teamcity/src/builds/test/XPackJest.kt create mode 100644 .teamcity/src/projects/EsSnapshots.kt create mode 100644 .teamcity/src/projects/Kibana.kt create mode 100644 .teamcity/src/templates/DefaultTemplate.kt create mode 100644 .teamcity/src/templates/KibanaTemplate.kt create mode 100644 .teamcity/src/vcs/Elasticsearch.kt create mode 100644 .teamcity/src/vcs/Kibana.kt create mode 100644 .teamcity/tests/projects/KibanaTest.kt diff --git a/.ci/teamcity/bootstrap.sh b/.ci/teamcity/bootstrap.sh new file mode 100755 index 000000000000..adb884ca064b --- /dev/null +++ b/.ci/teamcity/bootstrap.sh @@ -0,0 +1,21 @@ +#!/usr/bin/env bash + +set -euo pipefail + +source "$(dirname "${0}")/util.sh" + +tc_start_block "Bootstrap" + +tc_start_block "yarn install and kbn bootstrap" +verify_no_git_changes yarn kbn bootstrap --prefer-offline +tc_end_block "yarn install and kbn bootstrap" + +tc_start_block "build kbn-pm" +verify_no_git_changes yarn kbn run build -i @kbn/pm +tc_end_block "build kbn-pm" + +tc_start_block "build plugin list docs" +verify_no_git_changes node scripts/build_plugin_list_docs +tc_end_block "build plugin list docs" + +tc_end_block "Bootstrap" diff --git a/.ci/teamcity/checks/bundle_limits.sh b/.ci/teamcity/checks/bundle_limits.sh new file mode 100755 index 000000000000..3f7daef6d047 --- /dev/null +++ b/.ci/teamcity/checks/bundle_limits.sh @@ -0,0 +1,7 @@ +#!/usr/bin/env bash + +set -euo pipefail + +source "$(dirname "${0}")/../util.sh" + +node scripts/build_kibana_platform_plugins --validate-limits diff --git a/.ci/teamcity/checks/doc_api_changes.sh b/.ci/teamcity/checks/doc_api_changes.sh new file mode 100755 index 000000000000..821647a39441 --- /dev/null +++ b/.ci/teamcity/checks/doc_api_changes.sh @@ -0,0 +1,7 @@ +#!/usr/bin/env bash + +set -euo pipefail + +source "$(dirname "${0}")/../util.sh" + +yarn run grunt run:checkDocApiChanges diff --git a/.ci/teamcity/checks/file_casing.sh b/.ci/teamcity/checks/file_casing.sh new file mode 100755 index 000000000000..66578a4970fe --- /dev/null +++ b/.ci/teamcity/checks/file_casing.sh @@ -0,0 +1,7 @@ +#!/usr/bin/env bash + +set -euo pipefail + +source "$(dirname "${0}")/../util.sh" + +yarn run grunt run:checkFileCasing diff --git a/.ci/teamcity/checks/i18n.sh b/.ci/teamcity/checks/i18n.sh new file mode 100755 index 000000000000..f269816cf6b9 --- /dev/null +++ b/.ci/teamcity/checks/i18n.sh @@ -0,0 +1,7 @@ +#!/usr/bin/env bash + +set -euo pipefail + +source "$(dirname "${0}")/../util.sh" + +yarn run grunt run:i18nCheck diff --git a/.ci/teamcity/checks/licenses.sh b/.ci/teamcity/checks/licenses.sh new file mode 100755 index 000000000000..2baca8707463 --- /dev/null +++ b/.ci/teamcity/checks/licenses.sh @@ -0,0 +1,7 @@ +#!/usr/bin/env bash + +set -euo pipefail + +source "$(dirname "${0}")/../util.sh" + +yarn run grunt run:licenses diff --git a/.ci/teamcity/checks/telemetry.sh b/.ci/teamcity/checks/telemetry.sh new file mode 100755 index 000000000000..6413584d2057 --- /dev/null +++ b/.ci/teamcity/checks/telemetry.sh @@ -0,0 +1,7 @@ +#!/usr/bin/env bash + +set -euo pipefail + +source "$(dirname "${0}")/../util.sh" + +yarn run grunt run:telemetryCheck diff --git a/.ci/teamcity/checks/test_hardening.sh b/.ci/teamcity/checks/test_hardening.sh new file mode 100755 index 000000000000..21ee68e5ade7 --- /dev/null +++ b/.ci/teamcity/checks/test_hardening.sh @@ -0,0 +1,7 @@ +#!/usr/bin/env bash + +set -euo pipefail + +source "$(dirname "${0}")/../util.sh" + +yarn run grunt run:test_hardening diff --git a/.ci/teamcity/checks/ts_projects.sh b/.ci/teamcity/checks/ts_projects.sh new file mode 100755 index 000000000000..8afc195fee55 --- /dev/null +++ b/.ci/teamcity/checks/ts_projects.sh @@ -0,0 +1,7 @@ +#!/usr/bin/env bash + +set -euo pipefail + +source "$(dirname "${0}")/../util.sh" + +yarn run grunt run:checkTsProjects diff --git a/.ci/teamcity/checks/type_check.sh b/.ci/teamcity/checks/type_check.sh new file mode 100755 index 000000000000..da8ae3373d97 --- /dev/null +++ b/.ci/teamcity/checks/type_check.sh @@ -0,0 +1,7 @@ +#!/usr/bin/env bash + +set -euo pipefail + +source "$(dirname "${0}")/../util.sh" + +yarn run grunt run:typeCheck diff --git a/.ci/teamcity/checks/verify_dependency_versions.sh b/.ci/teamcity/checks/verify_dependency_versions.sh new file mode 100755 index 000000000000..4c2ddf5ce861 --- /dev/null +++ b/.ci/teamcity/checks/verify_dependency_versions.sh @@ -0,0 +1,7 @@ +#!/usr/bin/env bash + +set -euo pipefail + +source "$(dirname "${0}")/../util.sh" + +yarn run grunt run:verifyDependencyVersions diff --git a/.ci/teamcity/checks/verify_notice.sh b/.ci/teamcity/checks/verify_notice.sh new file mode 100755 index 000000000000..8571e0bbceb1 --- /dev/null +++ b/.ci/teamcity/checks/verify_notice.sh @@ -0,0 +1,7 @@ +#!/usr/bin/env bash + +set -euo pipefail + +source "$(dirname "${0}")/../util.sh" + +yarn run grunt run:verifyNotice diff --git a/.ci/teamcity/ci_stats.js b/.ci/teamcity/ci_stats.js new file mode 100644 index 000000000000..2953661eca1f --- /dev/null +++ b/.ci/teamcity/ci_stats.js @@ -0,0 +1,59 @@ +const https = require('https'); +const token = process.env.CI_STATS_TOKEN; +const host = process.env.CI_STATS_HOST; + +const request = (url, options, data = null) => { + const httpOptions = { + ...options, + headers: { + ...(options.headers || {}), + Authorization: `token ${token}`, + }, + }; + + return new Promise((resolve, reject) => { + console.log(`Calling https://${host}${url}`); + + const req = https.request(`https://${host}${url}`, httpOptions, (res) => { + if (res.statusCode < 200 || res.statusCode >= 300) { + return reject(new Error(`Status Code: ${res.statusCode}`)); + } + + const data = []; + res.on('data', (d) => { + data.push(d); + }) + + res.on('end', () => { + try { + let resp = Buffer.concat(data).toString(); + + try { + if (resp.trim()) { + resp = JSON.parse(resp); + } + } catch (ex) { + console.error(ex); + } + + resolve(resp); + } catch (ex) { + reject(ex); + } + }); + }) + + req.on('error', reject); + + if (data) { + req.write(JSON.stringify(data)); + } + + req.end(); + }); +} + +module.exports = { + get: (url) => request(url, { method: 'GET' }), + post: (url, data) => request(url, { method: 'POST' }, data), +} diff --git a/.ci/teamcity/ci_stats_complete.js b/.ci/teamcity/ci_stats_complete.js new file mode 100644 index 000000000000..0df9329167ff --- /dev/null +++ b/.ci/teamcity/ci_stats_complete.js @@ -0,0 +1,18 @@ +const ciStats = require('./ci_stats'); + +// This might be better as an API call in the future. +// Instead, it relies on a separate step setting the BUILD_STATUS env var. BUILD_STATUS is not something provided by TeamCity. +const BUILD_STATUS = process.env.BUILD_STATUS === 'SUCCESS' ? 'SUCCESS' : 'FAILURE'; + +(async () => { + try { + if (process.env.CI_STATS_BUILD_ID) { + await ciStats.post(`/v1/build/_complete?id=${process.env.CI_STATS_BUILD_ID}`, { + result: BUILD_STATUS, + }); + } + } catch (ex) { + console.error(ex); + process.exit(1); + } +})(); diff --git a/.ci/teamcity/default/accessibility.sh b/.ci/teamcity/default/accessibility.sh new file mode 100755 index 000000000000..2868db9d067b --- /dev/null +++ b/.ci/teamcity/default/accessibility.sh @@ -0,0 +1,16 @@ +#!/bin/bash + +set -euo pipefail + +source "$(dirname "${0}")/../util.sh" + +export JOB=kibana-default-accessibility +export KIBANA_INSTALL_DIR="$PARENT_DIR/build/kibana-build-default" + +cd "$XPACK_DIR" + +checks-reporter-with-killswitch "X-Pack accessibility tests" \ + node scripts/functional_tests \ + --debug --bail \ + --kibana-install-dir "$KIBANA_INSTALL_DIR" \ + --config test/accessibility/config.ts diff --git a/.ci/teamcity/default/build.sh b/.ci/teamcity/default/build.sh new file mode 100755 index 000000000000..af90e24ef5fe --- /dev/null +++ b/.ci/teamcity/default/build.sh @@ -0,0 +1,31 @@ +#!/bin/bash + +set -euo pipefail + +source "$(dirname "${0}")/../util.sh" + +tc_start_block "Build Platform Plugins" +node scripts/build_kibana_platform_plugins \ + --scan-dir "$KIBANA_DIR/test/plugin_functional/plugins" \ + --scan-dir "$KIBANA_DIR/test/common/fixtures/plugins" \ + --scan-dir "$XPACK_DIR/test/plugin_functional/plugins" \ + --scan-dir "$XPACK_DIR/test/functional_with_es_ssl/fixtures/plugins" \ + --scan-dir "$XPACK_DIR/test/alerting_api_integration/plugins" \ + --scan-dir "$XPACK_DIR/test/plugin_api_integration/plugins" \ + --scan-dir "$XPACK_DIR/test/plugin_api_perf/plugins" \ + --scan-dir "$XPACK_DIR/test/licensing_plugin/plugins" \ + --verbose +tc_end_block "Build Platform Plugins" + +export KBN_NP_PLUGINS_BUILT=true + +tc_start_block "Build Default Distribution" + +cd "$KIBANA_DIR" +node scripts/build --debug --no-oss +linuxBuild="$(find "$KIBANA_DIR/target" -name 'kibana-*-linux-x86_64.tar.gz')" +installDir="$KIBANA_DIR/install/kibana" +mkdir -p "$installDir" +tar -xzf "$linuxBuild" -C "$installDir" --strip=1 + +tc_end_block "Build Default Distribution" diff --git a/.ci/teamcity/default/build_plugins.sh b/.ci/teamcity/default/build_plugins.sh new file mode 100755 index 000000000000..76c553b4f8fa --- /dev/null +++ b/.ci/teamcity/default/build_plugins.sh @@ -0,0 +1,20 @@ +#!/bin/bash + +set -euo pipefail + +source "$(dirname "${0}")/../util.sh" + +tc_start_block "Build Platform Plugins" +node scripts/build_kibana_platform_plugins \ + --scan-dir "$KIBANA_DIR/test/plugin_functional/plugins" \ + --scan-dir "$KIBANA_DIR/test/common/fixtures/plugins" \ + --scan-dir "$XPACK_DIR/test/plugin_functional/plugins" \ + --scan-dir "$XPACK_DIR/test/functional_with_es_ssl/fixtures/plugins" \ + --scan-dir "$XPACK_DIR/test/alerting_api_integration/plugins" \ + --scan-dir "$XPACK_DIR/test/plugin_api_integration/plugins" \ + --scan-dir "$XPACK_DIR/test/plugin_api_perf/plugins" \ + --scan-dir "$XPACK_DIR/test/licensing_plugin/plugins" \ + --verbose +tc_end_block "Build Platform Plugins" + +tc_set_env KBN_NP_PLUGINS_BUILT true diff --git a/.ci/teamcity/default/ci_group.sh b/.ci/teamcity/default/ci_group.sh new file mode 100755 index 000000000000..26c2c563210e --- /dev/null +++ b/.ci/teamcity/default/ci_group.sh @@ -0,0 +1,17 @@ +#!/bin/bash + +set -euo pipefail + +source "$(dirname "${0}")/../util.sh" + +export CI_GROUP="$1" +export JOB=kibana-default-ciGroup${CI_GROUP} +export KIBANA_INSTALL_DIR="$PARENT_DIR/build/kibana-build-default" + +cd "$XPACK_DIR" + +checks-reporter-with-killswitch "Default Distro Chrome Functional tests / Group ${CI_GROUP}" \ + node scripts/functional_tests \ + --debug --bail \ + --kibana-install-dir "$KIBANA_INSTALL_DIR" \ + --include-tag "ciGroup$CI_GROUP" diff --git a/.ci/teamcity/default/firefox.sh b/.ci/teamcity/default/firefox.sh new file mode 100755 index 000000000000..5922a72bd5e8 --- /dev/null +++ b/.ci/teamcity/default/firefox.sh @@ -0,0 +1,18 @@ +#!/bin/bash + +set -euo pipefail + +source "$(dirname "${0}")/../util.sh" + +export JOB=kibana-default-firefoxSmoke +export KIBANA_INSTALL_DIR="$PARENT_DIR/build/kibana-build-default" + +cd "$XPACK_DIR" + +checks-reporter-with-killswitch "X-Pack firefox smoke test" \ + node scripts/functional_tests \ + --debug --bail \ + --kibana-install-dir "$KIBANA_INSTALL_DIR" \ + --include-tag "includeFirefox" \ + --config test/functional/config.firefox.js \ + --config test/functional_embedded/config.firefox.ts diff --git a/.ci/teamcity/default/saved_object_field_metrics.sh b/.ci/teamcity/default/saved_object_field_metrics.sh new file mode 100755 index 000000000000..f5b57ce3b06e --- /dev/null +++ b/.ci/teamcity/default/saved_object_field_metrics.sh @@ -0,0 +1,16 @@ +#!/bin/bash + +set -euo pipefail + +source "$(dirname "${0}")/../util.sh" + +export JOB=kibana-default-savedObjectFieldMetrics +export KIBANA_INSTALL_DIR="$PARENT_DIR/build/kibana-build-default" + +cd "$XPACK_DIR" + +checks-reporter-with-killswitch "Capture Kibana Saved Objects field count metrics" \ + node scripts/functional_tests \ + --debug --bail \ + --kibana-install-dir "$KIBANA_INSTALL_DIR" \ + --config test/saved_objects_field_count/config.ts diff --git a/.ci/teamcity/default/security_solution.sh b/.ci/teamcity/default/security_solution.sh new file mode 100755 index 000000000000..46048f6c82d5 --- /dev/null +++ b/.ci/teamcity/default/security_solution.sh @@ -0,0 +1,16 @@ +#!/bin/bash + +set -euo pipefail + +source "$(dirname "${0}")/../util.sh" + +export JOB=kibana-default-securitySolution +export KIBANA_INSTALL_DIR="$PARENT_DIR/build/kibana-build-default" + +cd "$XPACK_DIR" + +checks-reporter-with-killswitch "Security Solution Cypress Tests" \ + node scripts/functional_tests \ + --debug --bail \ + --kibana-install-dir "$KIBANA_INSTALL_DIR" \ + --config test/security_solution_cypress/cli_config.ts diff --git a/.ci/teamcity/es_snapshots/build.sh b/.ci/teamcity/es_snapshots/build.sh new file mode 100755 index 000000000000..f983713e80f4 --- /dev/null +++ b/.ci/teamcity/es_snapshots/build.sh @@ -0,0 +1,45 @@ +#!/bin/bash + +set -euo pipefail + +source "$(dirname "${0}")/../util.sh" + +cd .. +destination="$(pwd)/es-build" +mkdir -p "$destination" + +cd elasticsearch + +# These turn off automation in the Elasticsearch repo +export BUILD_NUMBER="" +export JENKINS_URL="" +export BUILD_URL="" +export JOB_NAME="" +export NODE_NAME="" + +# Reads the ES_BUILD_JAVA env var out of .ci/java-versions.properties and exports it +export "$(grep '^ES_BUILD_JAVA' .ci/java-versions.properties | xargs)" + +export PATH="$HOME/.java/$ES_BUILD_JAVA/bin:$PATH" +export JAVA_HOME="$HOME/.java/$ES_BUILD_JAVA" + +tc_start_block "Build Elasticsearch" +./gradlew -Dbuild.docker=true assemble --parallel +tc_end_block "Build Elasticsearch" + +tc_start_block "Create distribution archives" +find distribution -type f \( -name 'elasticsearch-*-*-*-*.tar.gz' -o -name 'elasticsearch-*-*-*-*.zip' \) -not -path '*no-jdk*' -not -path '*build-context*' -exec cp {} "$destination" \; +tc_end_block "Create distribution archives" + +ls -alh "$destination" + +tc_start_block "Create docker image archives" +docker images "docker.elastic.co/elasticsearch/elasticsearch" +docker images "docker.elastic.co/elasticsearch/elasticsearch" --format "{{.Tag}}" | xargs -n1 echo 'docker save docker.elastic.co/elasticsearch/elasticsearch:${0} | gzip > ../es-build/elasticsearch-${0}-docker-image.tar.gz' +docker images "docker.elastic.co/elasticsearch/elasticsearch" --format "{{.Tag}}" | xargs -n1 bash -c 'docker save docker.elastic.co/elasticsearch/elasticsearch:${0} | gzip > ../es-build/elasticsearch-${0}-docker-image.tar.gz' +tc_end_block "Create docker image archives" + +cd "$destination" + +find ./* -exec bash -c "shasum -a 512 {} > {}.sha512" \; +ls -alh "$destination" diff --git a/.ci/teamcity/es_snapshots/create_manifest.js b/.ci/teamcity/es_snapshots/create_manifest.js new file mode 100644 index 000000000000..63e54987f788 --- /dev/null +++ b/.ci/teamcity/es_snapshots/create_manifest.js @@ -0,0 +1,82 @@ +const fs = require('fs'); +const { execSync } = require('child_process'); + +(async () => { + const destination = process.argv[2] || __dirname + '/test'; + + let ES_BRANCH = process.env.ELASTICSEARCH_BRANCH; + let GIT_COMMIT = process.env.ELASTICSEARCH_GIT_COMMIT; + let GIT_COMMIT_SHORT = execSync(`git rev-parse --short '${GIT_COMMIT}'`).toString().trim(); + + let VERSION = ''; + let SNAPSHOT_ID = ''; + let DESTINATION = ''; + + const now = new Date() + + // format: yyyyMMdd-HHmmss + const date = [ + now.getFullYear(), + (now.getMonth()+1).toString().padStart(2, '0'), + now.getDate().toString().padStart(2, '0'), + '-', + now.getHours().toString().padStart(2, '0'), + now.getMinutes().toString().padStart(2, '0'), + now.getSeconds().toString().padStart(2, '0'), + ].join('') + + try { + const files = fs.readdirSync(destination); + const manifestEntries = files + .filter(f => !f.match(/.sha512$/)) + .filter(f => !f.match(/.json$/)) + .map(filename => { + const parts = filename.replace("elasticsearch-oss", "oss").split("-") + + VERSION = VERSION || parts[1]; + SNAPSHOT_ID = SNAPSHOT_ID || `${date}_${GIT_COMMIT_SHORT}`; + DESTINATION = DESTINATION || `${VERSION}/archives/${SNAPSHOT_ID}`; + + return { + filename: filename, + checksum: filename + '.sha512', + url: `https://storage.googleapis.com/kibana-ci-es-snapshots-daily-teamcity/${DESTINATION}/${filename}`, + version: parts[1], + platform: parts[3], + architecture: parts[4].split('.')[0], + license: parts[0] == 'oss' ? 'oss' : 'default', + } + }); + + const manifest = { + id: SNAPSHOT_ID, + bucket: `kibana-ci-es-snapshots-daily-teamcity/${DESTINATION}`.toString(), + branch: ES_BRANCH, + sha: GIT_COMMIT, + sha_short: GIT_COMMIT_SHORT, + version: VERSION, + generated: now.toISOString(), + archives: manifestEntries, + }; + + const manifestJSON = JSON.stringify(manifest, null, 2); + fs.writeFileSync(`${destination}/manifest.json`, manifestJSON); + + execSync(` + set -euo pipefail + cd "${destination}" + gsutil -m cp -r *.* gs://kibana-ci-es-snapshots-daily-teamcity/${DESTINATION} + cp manifest.json manifest-latest.json + gsutil cp manifest-latest.json gs://kibana-ci-es-snapshots-daily-teamcity/${VERSION} + `, { shell: '/bin/bash' }); + + console.log(`##teamcity[setParameter name='env.ES_SNAPSHOT_MANIFEST' value='https://storage.googleapis.com/kibana-ci-es-snapshots-daily-teamcity/${DESTINATION}/manifest.json']`); + console.log(`##teamcity[setParameter name='env.ES_SNAPSHOT_VERSION' value='${VERSION}']`); + console.log(`##teamcity[setParameter name='env.ES_SNAPSHOT_ID' value='${SNAPSHOT_ID}']`); + + console.log(`##teamcity[buildNumber '{build.number}-${VERSION}-${SNAPSHOT_ID}']`); + } catch (ex) { + console.error(ex); + process.exit(1); + } +})(); diff --git a/.ci/teamcity/es_snapshots/promote_manifest.js b/.ci/teamcity/es_snapshots/promote_manifest.js new file mode 100644 index 000000000000..bcc79e696d78 --- /dev/null +++ b/.ci/teamcity/es_snapshots/promote_manifest.js @@ -0,0 +1,53 @@ +const fs = require('fs'); +const { execSync } = require('child_process'); + +const BASE_BUCKET_DAILY = 'kibana-ci-es-snapshots-daily-teamcity'; +const BASE_BUCKET_PERMANENT = 'kibana-ci-es-snapshots-daily-teamcity/permanent'; + +(async () => { + try { + const MANIFEST_URL = process.argv[2]; + + if (!MANIFEST_URL) { + throw Error('Manifest URL missing'); + } + + if (!fs.existsSync('snapshot-promotion')) { + fs.mkdirSync('snapshot-promotion'); + } + process.chdir('snapshot-promotion'); + + execSync(`curl '${MANIFEST_URL}' > manifest.json`); + + const manifest = JSON.parse(fs.readFileSync('manifest.json')); + const { id, bucket, version } = manifest; + + console.log(`##teamcity[buildNumber '{build.number}-${version}-${id}']`); + + const manifestPermanent = { + ...manifest, + bucket: bucket.replace(BASE_BUCKET_DAILY, BASE_BUCKET_PERMANENT), + }; + + fs.writeFileSync('manifest-permanent.json', JSON.stringify(manifestPermanent, null, 2)); + + execSync( + ` + set -euo pipefail + + cp manifest.json manifest-latest-verified.json + gsutil cp manifest-latest-verified.json gs://${BASE_BUCKET_DAILY}/${version}/ + + rm manifest.json + cp manifest-permanent.json manifest.json + gsutil -m cp -r gs://${bucket}/* gs://${BASE_BUCKET_PERMANENT}/${version}/ + gsutil cp manifest.json gs://${BASE_BUCKET_PERMANENT}/${version}/ + + `, + { shell: '/bin/bash' } + ); + } catch (ex) { + console.error(ex); + process.exit(1); + } +})(); diff --git a/.ci/teamcity/oss/accessibility.sh b/.ci/teamcity/oss/accessibility.sh new file mode 100755 index 000000000000..09693d7ebdc5 --- /dev/null +++ b/.ci/teamcity/oss/accessibility.sh @@ -0,0 +1,14 @@ +#!/bin/bash + +set -euo pipefail + +source "$(dirname "${0}")/../util.sh" + +export JOB=kibana-oss-accessibility +export KIBANA_INSTALL_DIR="$PARENT_DIR/build/kibana-build-oss" + +checks-reporter-with-killswitch "Kibana accessibility tests" \ + node scripts/functional_tests \ + --debug --bail \ + --kibana-install-dir "$KIBANA_INSTALL_DIR" \ + --config test/accessibility/config.ts diff --git a/.ci/teamcity/oss/build.sh b/.ci/teamcity/oss/build.sh new file mode 100755 index 000000000000..3ef14b166335 --- /dev/null +++ b/.ci/teamcity/oss/build.sh @@ -0,0 +1,24 @@ +#!/bin/bash + +set -euo pipefail + +source "$(dirname "${0}")/../util.sh" + +tc_start_block "Build Platform Plugins" +node scripts/build_kibana_platform_plugins \ + --oss \ + --filter '!alertingExample' \ + --scan-dir "$KIBANA_DIR/test/plugin_functional/plugins" \ + --scan-dir "$KIBANA_DIR/test/interpreter_functional/plugins" \ + --scan-dir "$KIBANA_DIR/test/common/fixtures/plugins" \ + --verbose +tc_end_block "Build Platform Plugins" + +export KBN_NP_PLUGINS_BUILT=true + +tc_start_block "Build OSS Distribution" +node scripts/build --debug --oss + +# Renaming the build directory to a static one, so that we can put a static one in the TeamCity artifact rules +mv build/oss/kibana-*-SNAPSHOT-linux-x86_64 build/oss/kibana-build-oss +tc_end_block "Build OSS Distribution" diff --git a/.ci/teamcity/oss/build_plugins.sh b/.ci/teamcity/oss/build_plugins.sh new file mode 100755 index 000000000000..28e3c9247f1d --- /dev/null +++ b/.ci/teamcity/oss/build_plugins.sh @@ -0,0 +1,16 @@ +#!/bin/bash + +set -euo pipefail + +source "$(dirname "${0}")/../util.sh" + +tc_start_block "Build Platform Plugins - OSS" + +node scripts/build_kibana_platform_plugins \ + --oss \ + --filter '!alertingExample' \ + --scan-dir "$KIBANA_DIR/test/plugin_functional/plugins" \ + --scan-dir "$KIBANA_DIR/test/interpreter_functional/plugins" \ + --scan-dir "$KIBANA_DIR/test/common/fixtures/plugins" \ + --verbose +tc_end_block "Build Platform Plugins - OSS" diff --git a/.ci/teamcity/oss/ci_group.sh b/.ci/teamcity/oss/ci_group.sh new file mode 100755 index 000000000000..3b2fb7ea912b --- /dev/null +++ b/.ci/teamcity/oss/ci_group.sh @@ -0,0 +1,15 @@ +#!/bin/bash + +set -euo pipefail + +source "$(dirname "${0}")/../util.sh" + +export CI_GROUP="$1" +export JOB="kibana-ciGroup$CI_GROUP" +export KIBANA_INSTALL_DIR="$PARENT_DIR/build/kibana-build-oss" + +checks-reporter-with-killswitch "Functional tests / Group $CI_GROUP" \ + node scripts/functional_tests \ + --debug --bail \ + --kibana-install-dir "$KIBANA_INSTALL_DIR" \ + --include-tag "ciGroup$CI_GROUP" diff --git a/.ci/teamcity/oss/firefox.sh b/.ci/teamcity/oss/firefox.sh new file mode 100755 index 000000000000..5e2a6c17c005 --- /dev/null +++ b/.ci/teamcity/oss/firefox.sh @@ -0,0 +1,15 @@ +#!/bin/bash + +set -euo pipefail + +source "$(dirname "${0}")/../util.sh" + +export JOB=kibana-firefoxSmoke +export KIBANA_INSTALL_DIR="$PARENT_DIR/build/kibana-build-oss" + +checks-reporter-with-killswitch "Firefox smoke test" \ + node scripts/functional_tests \ + --bail --debug \ + --kibana-install-dir "$KIBANA_INSTALL_DIR" \ + --include-tag "includeFirefox" \ + --config test/functional/config.firefox.js diff --git a/.ci/teamcity/oss/plugin_functional.sh b/.ci/teamcity/oss/plugin_functional.sh new file mode 100755 index 000000000000..41ff549945c0 --- /dev/null +++ b/.ci/teamcity/oss/plugin_functional.sh @@ -0,0 +1,18 @@ +#!/bin/bash + +set -euo pipefail + +source "$(dirname "${0}")/../util.sh" + +export JOB=kibana-oss-pluginFunctional +export KIBANA_INSTALL_DIR="$PARENT_DIR/build/kibana-build-oss" + +cd test/plugin_functional/plugins/kbn_sample_panel_action +if [[ ! -d "target" ]]; then + yarn build +fi +cd - + +yarn run grunt run:pluginFunctionalTestsRelease --from=source +yarn run grunt run:exampleFunctionalTestsRelease --from=source +yarn run grunt run:interpreterFunctionalTestsRelease diff --git a/.ci/teamcity/setup_ci_stats.js b/.ci/teamcity/setup_ci_stats.js new file mode 100644 index 000000000000..6b381530d9bb --- /dev/null +++ b/.ci/teamcity/setup_ci_stats.js @@ -0,0 +1,33 @@ +const ciStats = require('./ci_stats'); + +(async () => { + try { + const build = await ciStats.post('/v1/build', { + jenkinsJobName: process.env.TEAMCITY_BUILDCONF_NAME, + jenkinsJobId: process.env.TEAMCITY_BUILD_ID, + jenkinsUrl: process.env.TEAMCITY_BUILD_URL, + prId: process.env.GITHUB_PR_NUMBER || null, + }); + + const config = { + apiUrl: `https://${process.env.CI_STATS_HOST}`, + apiToken: process.env.CI_STATS_TOKEN, + buildId: build.id, + }; + + const configJson = JSON.stringify(config); + process.env.KIBANA_CI_STATS_CONFIG = configJson; + console.log(`\n##teamcity[setParameter name='env.KIBANA_CI_STATS_CONFIG' display='hidden' password='true' value='${configJson}']\n`); + console.log(`\n##teamcity[setParameter name='env.CI_STATS_BUILD_ID' value='${build.id}']\n`); + + await ciStats.post(`/v1/git_info?buildId=${build.id}`, { + branch: process.env.GIT_BRANCH.replace(/^(refs\/heads\/|origin\/)/, ''), + commit: process.env.GIT_COMMIT, + targetBranch: process.env.GITHUB_PR_TARGET_BRANCH || null, + mergeBase: null, // TODO + }); + } catch (ex) { + console.error(ex); + process.exit(1); + } +})(); diff --git a/.ci/teamcity/setup_env.sh b/.ci/teamcity/setup_env.sh new file mode 100755 index 000000000000..f662d36247a2 --- /dev/null +++ b/.ci/teamcity/setup_env.sh @@ -0,0 +1,53 @@ +#!/usr/bin/env bash + +set -euo pipefail + +source "$(dirname "${0}")/util.sh" + +tc_set_env KIBANA_DIR "$(cd "$(dirname "$0")/../.." && pwd)" +tc_set_env XPACK_DIR "$KIBANA_DIR/x-pack" + +tc_set_env CACHE_DIR "$HOME/.kibana" +tc_set_env PARENT_DIR "$(cd "$KIBANA_DIR/.."; pwd)" +tc_set_env WORKSPACE "${WORKSPACE:-$PARENT_DIR}" + +tc_set_env KIBANA_PKG_BRANCH "$(jq -r .branch "$KIBANA_DIR/package.json")" +tc_set_env KIBANA_BASE_BRANCH "$KIBANA_PKG_BRANCH" + +tc_set_env GECKODRIVER_CDNURL "https://us-central1-elastic-kibana-184716.cloudfunctions.net/kibana-ci-proxy-cache" +tc_set_env CHROMEDRIVER_CDNURL "https://us-central1-elastic-kibana-184716.cloudfunctions.net/kibana-ci-proxy-cache" +tc_set_env RE2_DOWNLOAD_MIRROR "https://us-central1-elastic-kibana-184716.cloudfunctions.net/kibana-ci-proxy-cache" +tc_set_env CYPRESS_DOWNLOAD_MIRROR "https://us-central1-elastic-kibana-184716.cloudfunctions.net/kibana-ci-proxy-cache/cypress" + +tc_set_env NODE_OPTIONS "${NODE_OPTIONS:-} --max-old-space-size=4096" + +tc_set_env FORCE_COLOR 1 +tc_set_env TEST_BROWSER_HEADLESS 1 + +tc_set_env ELASTIC_APM_ENVIRONMENT ci + +if [[ "${KIBANA_CI_REPORTER_KEY_BASE64-}" ]]; then + tc_set_env KIBANA_CI_REPORTER_KEY "$(echo "$KIBANA_CI_REPORTER_KEY_BASE64" | base64 -d)" +fi + +if is_pr; then + tc_set_env CHECKS_REPORTER_ACTIVE true + + # These can be removed once we're not supporting Jenkins and TeamCity at the same time + # These are primarily used by github checks reporter and can be configured via /github_checks_api.json + tc_set_env ghprbGhRepository "elastic/kibana" # TODO? + tc_set_env ghprbActualCommit "$GITHUB_PR_TRIGGERED_SHA" + tc_set_env BUILD_URL "$TEAMCITY_BUILD_URL" +else + tc_set_env CHECKS_REPORTER_ACTIVE false +fi + +tc_set_env FLEET_PACKAGE_REGISTRY_PORT 6104 # Any unused port is fine, used by ingest manager tests + +if [[ "$(which google-chrome-stable)" || "$(which google-chrome)" ]]; then + echo "Chrome detected, setting DETECT_CHROMEDRIVER_VERSION=true" + tc_set_env DETECT_CHROMEDRIVER_VERSION true + tc_set_env CHROMEDRIVER_FORCE_DOWNLOAD true +else + echo "Chrome not detected, installing default chromedriver binary for the package version" +fi diff --git a/.ci/teamcity/setup_node.sh b/.ci/teamcity/setup_node.sh new file mode 100755 index 000000000000..b805a2aa6fe6 --- /dev/null +++ b/.ci/teamcity/setup_node.sh @@ -0,0 +1,42 @@ +#!/usr/bin/env bash + +set -euo pipefail + +source "$(dirname "${0}")/util.sh" + +tc_start_block "Setup Node" + +tc_set_env NODE_VERSION "$(cat "$KIBANA_DIR/.node-version")" +tc_set_env NODE_DIR "$CACHE_DIR/node/$NODE_VERSION" +tc_set_env NODE_BIN_DIR "$NODE_DIR/bin" +tc_set_env YARN_OFFLINE_CACHE "$CACHE_DIR/yarn-offline-cache" + +if [[ ! -d "$NODE_DIR" ]]; then + nodeUrl="https://us-central1-elastic-kibana-184716.cloudfunctions.net/kibana-ci-proxy-cache/dist/v$NODE_VERSION/node-v$NODE_VERSION-linux-x64.tar.gz" + + echo "node.js v$NODE_VERSION not found at $NODE_DIR, downloading from $nodeUrl" + + mkdir -p "$NODE_DIR" + curl --silent -L "$nodeUrl" | tar -xz -C "$NODE_DIR" --strip-components=1 +else + echo "node.js v$NODE_VERSION already installed to $NODE_DIR, re-using" + ls -alh "$NODE_BIN_DIR" +fi + +tc_set_env PATH "$NODE_BIN_DIR:$PATH" + +tc_end_block "Setup Node" +tc_start_block "Setup Yarn" + +tc_set_env YARN_VERSION "$(node -e "console.log(String(require('./package.json').engines.yarn || '').replace(/^[^\d]+/,''))")" + +if [[ ! $(which yarn) || $(yarn --version) != "$YARN_VERSION" ]]; then + npm install -g "yarn@^${YARN_VERSION}" +fi + +yarn config set yarn-offline-mirror "$YARN_OFFLINE_CACHE" + +tc_set_env YARN_GLOBAL_BIN "$(yarn global bin)" +tc_set_env PATH "$PATH:$YARN_GLOBAL_BIN" + +tc_end_block "Setup Yarn" diff --git a/.ci/teamcity/tests/mocha.sh b/.ci/teamcity/tests/mocha.sh new file mode 100755 index 000000000000..ea6c43c39e39 --- /dev/null +++ b/.ci/teamcity/tests/mocha.sh @@ -0,0 +1,7 @@ +#!/usr/bin/env bash + +set -euo pipefail + +source "$(dirname "${0}")/../util.sh" + +yarn run grunt run:mocha diff --git a/.ci/teamcity/tests/test_hardening.sh b/.ci/teamcity/tests/test_hardening.sh new file mode 100755 index 000000000000..21ee68e5ade7 --- /dev/null +++ b/.ci/teamcity/tests/test_hardening.sh @@ -0,0 +1,7 @@ +#!/usr/bin/env bash + +set -euo pipefail + +source "$(dirname "${0}")/../util.sh" + +yarn run grunt run:test_hardening diff --git a/.ci/teamcity/tests/test_projects.sh b/.ci/teamcity/tests/test_projects.sh new file mode 100755 index 000000000000..3feaa821424e --- /dev/null +++ b/.ci/teamcity/tests/test_projects.sh @@ -0,0 +1,7 @@ +#!/usr/bin/env bash + +set -euo pipefail + +source "$(dirname "${0}")/../util.sh" + +yarn run grunt run:test_projects diff --git a/.ci/teamcity/tests/xpack_list_cyclic_dependency.sh b/.ci/teamcity/tests/xpack_list_cyclic_dependency.sh new file mode 100755 index 000000000000..39f79f94744c --- /dev/null +++ b/.ci/teamcity/tests/xpack_list_cyclic_dependency.sh @@ -0,0 +1,8 @@ +#!/usr/bin/env bash + +set -euo pipefail + +source "$(dirname "${0}")/../util.sh" + +cd x-pack +checks-reporter-with-killswitch "X-Pack List cyclic dependency test" node plugins/lists/scripts/check_circular_deps diff --git a/.ci/teamcity/tests/xpack_siem_cyclic_dependency.sh b/.ci/teamcity/tests/xpack_siem_cyclic_dependency.sh new file mode 100755 index 000000000000..e3829c961fac --- /dev/null +++ b/.ci/teamcity/tests/xpack_siem_cyclic_dependency.sh @@ -0,0 +1,8 @@ +#!/usr/bin/env bash + +set -euo pipefail + +source "$(dirname "${0}")/../util.sh" + +cd x-pack +checks-reporter-with-killswitch "X-Pack SIEM cyclic dependency test" node plugins/security_solution/scripts/check_circular_deps diff --git a/.ci/teamcity/util.sh b/.ci/teamcity/util.sh new file mode 100755 index 000000000000..fe1afdf04c54 --- /dev/null +++ b/.ci/teamcity/util.sh @@ -0,0 +1,81 @@ +#!/usr/bin/env bash + +tc_escape() { + escaped="$1" + + # See https://www.jetbrains.com/help/teamcity/service-messages.html#Escaped+values + + escaped="$(echo "$escaped" | sed -z 's/|/||/g')" + escaped="$(echo "$escaped" | sed -z "s/'/|'/g")" + escaped="$(echo "$escaped" | sed -z 's/\[/|\[/g')" + escaped="$(echo "$escaped" | sed -z 's/\]/|\]/g')" + escaped="$(echo "$escaped" | sed -z 's/\n/|n/g')" + escaped="$(echo "$escaped" | sed -z 's/\r/|r/g')" + + echo "$escaped" +} + +# Sets up an environment variable locally, and also makes it available for subsequent steps in the build +# NOTE: env vars set up this way will be visible in the UI when logged in unless you set them up as blank password parameters ahead of time. +tc_set_env() { + export "$1"="$2" + echo "##teamcity[setParameter name='env.$1' value='$(tc_escape "$2")']" +} + +verify_no_git_changes() { + RED='\033[0;31m' + C_RESET='\033[0m' # Reset color + + "$@" + + GIT_CHANGES="$(git ls-files --modified)" + if [ "$GIT_CHANGES" ]; then + echo -e "\n${RED}ERROR: '$*' caused changes to the following files:${C_RESET}\n" + echo -e "$GIT_CHANGES\n" + exit 1 + fi +} + +tc_start_block() { + echo "##teamcity[blockOpened name='$1']" +} + +tc_end_block() { + echo "##teamcity[blockClosed name='$1']" +} + +checks-reporter-with-killswitch() { + if [ "$CHECKS_REPORTER_ACTIVE" == "true" ] ; then + yarn run github-checks-reporter "$@" + else + arguments=("$@"); + "${arguments[@]:1}"; + fi +} + +is_pr() { + [[ "${GITHUB_PR_NUMBER-}" ]] && return + false +} + +# This function is specifcally for retrying test runner steps one time +# A different solution should be used for retrying general steps (e.g. bootstrap) +tc_retry() { + tc_start_block "Retryable Step - Attempt #1" + "$@" || { + tc_end_block "Retryable Step - Attempt #1" + tc_start_block "Retryable Step - Attempt #2" + >&2 echo "First attempt failed. Retrying $*" + if "$@"; then + echo 'Second attempt successful' + echo "##teamcity[buildStatus status='SUCCESS' text='{build.status.text} with a flaky failure']" + echo "##teamcity[setParameter name='elastic.build.flaky' value='true']" + tc_end_block "Retryable Step - Attempt #2" + else + status="$?" + tc_end_block "Retryable Step - Attempt #2" + return "$status" + fi + } + tc_end_block "Retryable Step - Attempt #1" +} diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 5b43f9883a2c..93d49dc18d41 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -162,6 +162,8 @@ /src/cli/keystore/ @elastic/kibana-operations /src/legacy/server/warnings/ @elastic/kibana-operations /.ci/es-snapshots/ @elastic/kibana-operations +/.ci/teamcity/ @elastic/kibana-operations +/.teamcity/ @elastic/kibana-operations /vars/ @elastic/kibana-operations #CC# /packages/kbn-expect/ @elastic/kibana-operations diff --git a/.teamcity/.editorconfig b/.teamcity/.editorconfig new file mode 100644 index 000000000000..db789a8c72de --- /dev/null +++ b/.teamcity/.editorconfig @@ -0,0 +1,4 @@ +[*.{kt,kts}] +disabled_rules=no-wildcard-imports +indent_size=2 +kotlin_imports_layout=idea diff --git a/.teamcity/Kibana.png b/.teamcity/Kibana.png new file mode 100644 index 0000000000000000000000000000000000000000..c8f78f4575965ffd34a9ea18e31c8d6c61ff5600 GIT binary patch literal 129139 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 DA<>M< literal 0 HcmV?d00001 diff --git a/.teamcity/README.md b/.teamcity/README.md new file mode 100644 index 000000000000..77c0bc5bc4cd --- /dev/null +++ b/.teamcity/README.md @@ -0,0 +1,156 @@ +# Kibana TeamCity + +## Implemented so far + +- Project configuration with ability to provide configuration values that are unique per TeamCity instance (e.g. dev vs prod) +- Read-only configuration (no editing through the UI) +- Secrets stored in TeamCity outside of source control +- Setting secret environment variables (they get filtered from console if output on accident) +- GCP agent configurations + - One-time use agents + - Multiple agents configured, of different sizes (cpu, memory) + - Require specific agents per build configuration +- Unit testable DSL code +- Build artifact generation and consumption +- DSL Extensions of various kinds to easily share common configuration between build configurations in the same repo +- Barebones Slack notifications via plugin +- Dynamically creating environment variables / secrets at runtime for subsequent steps +- "Baseline CI" job that runs a subset of CI for every commit +- "Hourly CI" job that runs full CI hourly, if changes are detected. Re-uses builds that ran during "Baseline CI" for same commit +- Performance monitoring enabled for all jobs +- Jobs with multiple VCS roots (Kibana + Elasticsearch) +- GCS uploading using service account key file and gsutil +- Job that has a version string as an "output", rather than an artifact/file, with consumption in a different job +- Clone a list of jobs and modify dependencies/configuration for a second pipeline +- Promote/deploy a built artifact through the UI by selecting previously built artifact (or automatically build a new one and deploy if successful) +- Custom Build IDs using service messages + +## Pull Requests + +The `Pull Request` feature in TeamCity: + +- Automatically discovers pull request branches in GitHub + - Option to filter by contributor type (members of same org, org+external contributor, everyone) + - Option to filter by target branch (e.g. only discover Pull Requests targeting master) + - Works by essentially modifying the VCS root branch spec (so you should NOT add anything related to PRs to branch spec if you are using this) + - Draft PRs do get discovered +- Adds some Pull Request information to build overview pages +- Adds a few parameters available to build configurations: + - teamcity.pullRequest.number + - teamcity.pullRequest.title + - teamcity.pullRequest.source.branch + - teamcity.pullRequest.target.branch + - (Notice that source owner is not available - there's no information for forks) +- Requires a token for API interaction + +That's it. There's no interaction with labels/comments/etc. Triggering is handled via the standard triggering options. + +So, if you only want to: + +- Build on new commit (e.g. not via comment) or via the TeamCity UI +- Start builds for users not covered by the filter options using the TeamCity UI + +The Pull Request feature may be enough to cover your needs. Otherwise, you'll need something additional (an external bot, or a new teamcity plugin, etc). + +### Other PR notes + +- TeamCity doesn't have the ability to cancel currently-running builds when a new commit is pushed +- TeamCity does not add fork information (e.g. the owner) to build configuration parameters +- Builds CAN be triggered for branches not yet discovered + - You can turn off discovery altogether, and a branch will still be build-able. When triggered externally, it will show up in the UI and build. + +How to [trigger a build via API](https://www.jetbrains.com/help/teamcity/rest-api-reference.html#Triggering+a+Build): + +``` +POST https://teamcity-server/app/rest/buildQueue + + + + +``` + +and with additional properties: + +``` + + + + + + + +``` + +## Kibana Builds + +### Baseline CI + +- Generates baseline metrics needed for PR comparisons +- Only runs OSS and default builds, and generates default saved object field metrics +- Runs for each commit (each build should build a single commit) + +### Full CI + +- Runs everything in CI - all tests and builds +- Re-uses builds from Baseline CI if they are finished or in-progress +- Not generally triggered directly, is triggered by other jobs + +### Hourly CI + +- Triggers every hour and groups up all changes since the last run +- Runs whatever is in `Full CI` + +### Pull Request CI + +- Kibana TeamCity PR bot triggers this build for PRs (new commits, trigger comments) +- Sets many PR related parameters/env vars, then runs `Full CI` + +![Diagram](Kibana.png) + +### ES Snapshot Verification + +Build Configurations: + +- Build Snapshot +- Test Builds (e.g. OSS CI Group 1, Default CI Group 3, etc) +- Verify Snapshot +- Promote Snapshot +- Immediately Promote Snapshot + +Desires: + +- Build ES snapshot on a daily basis, run E2E tests against it, promote when successful +- Ability to easily promote old builds that have been verified +- Ability to run verification without promoting it + +#### Build Snapshot + +- checks out both Kibana and ES codebases +- builds ES artifacts +- uses scripts from Kibana repo to create JSON manifest and assemble snapshot files +- uploads artifacts to GCS +- sets parameters via service message that contains the snapshot URL, ID, version so they can be consumed by downstream jobs +- triggers on timer, once per day + +#### Test Builds + +- builds are clones of all "essential ci" functional and integration tests with irrelevant features disabled + - they are clones because runs of this build and runs of the essential ci versions for the same commit hash mean different things +- snapshot dependency on `Build Elasticsearch Snapshot` is added to clones +- set `env.ES_SNAPSHOT_MANIFEST` = `dep..ES_SNAPSHOT_MANIFEST` to "consume" the built artifact + +#### Verify Snapshot + +- composite build that contains all of the cloned test builds + +#### Promote Snapshot + +- snapshot dependency on `Build Snapshot` and `Verify Snapshot` +- uses scripts from Kibana repo to promote elasticsearch snapshot from `Build Snapshot` by updating manifest files in GCS +- triggers whenever a build of `Verify Snapshot` completes successfully + +#### Immediately Promote Snapshot + +- snapshot dependency only on `Build Snapshot` +- same as `Promote Snapshot` but skips testing +- can only be triggered manually diff --git a/.teamcity/pom.xml b/.teamcity/pom.xml new file mode 100644 index 000000000000..5fa068d0a92e --- /dev/null +++ b/.teamcity/pom.xml @@ -0,0 +1,128 @@ + + + + + 4.0.0 + Kibana Teamcity Config DSL Script + org.elastic.kibana + kibana-teamcity-dsl + 1.0-SNAPSHOT + + + org.jetbrains.teamcity + configs-dsl-kotlin-parent + 1.0-SNAPSHOT + + + + + jetbrains-all + https://download.jetbrains.com/teamcity-repository + + true + + + + teamcity-server + https://ci.elastic.dev/app/dsl-plugins-repository + + true + + + + + + + JetBrains + https://download.jetbrains.com/teamcity-repository + + + + + tests + src + + + kotlin-maven-plugin + org.jetbrains.kotlin + ${kotlin.version} + + + + compile + process-sources + + compile + + + + test-compile + process-test-sources + + test-compile + + + + + + org.jetbrains.teamcity + teamcity-configs-maven-plugin + ${teamcity.dsl.version} + + kotlin + target/generated-configs + + + + + + + + org.jetbrains.teamcity + configs-dsl-kotlin + ${teamcity.dsl.version} + compile + + + org.jetbrains.teamcity + configs-dsl-kotlin-plugins + 1.0-SNAPSHOT + pom + compile + + + org.jetbrains.kotlin + kotlin-stdlib-jdk8 + ${kotlin.version} + compile + + + org.jetbrains.kotlin + kotlin-script-runtime + ${kotlin.version} + compile + + + junit + junit + 4.13 + + + diff --git a/.teamcity/settings.kts b/.teamcity/settings.kts new file mode 100644 index 000000000000..ec1b1c6eb94e --- /dev/null +++ b/.teamcity/settings.kts @@ -0,0 +1,12 @@ +import jetbrains.buildServer.configs.kotlin.v2019_2.* +import projects.Kibana +import projects.KibanaConfiguration + +version = "2020.1" + +val config = KibanaConfiguration { + agentNetwork = DslContext.getParameter("agentNetwork", "teamcity") + agentSubnet = DslContext.getParameter("agentSubnet", "teamcity") +} + +project(Kibana(config)) diff --git a/.teamcity/src/Extensions.kt b/.teamcity/src/Extensions.kt new file mode 100644 index 000000000000..120b333d43e7 --- /dev/null +++ b/.teamcity/src/Extensions.kt @@ -0,0 +1,169 @@ +import jetbrains.buildServer.configs.kotlin.v2019_2.* +import jetbrains.buildServer.configs.kotlin.v2019_2.buildFeatures.notifications +import jetbrains.buildServer.configs.kotlin.v2019_2.buildSteps.ScriptBuildStep +import jetbrains.buildServer.configs.kotlin.v2019_2.buildSteps.script +import jetbrains.buildServer.configs.kotlin.v2019_2.ui.insert +import projects.kibanaConfiguration + +fun BuildFeatures.junit(dirs: String = "target/**/TEST-*.xml") { + feature { + type = "xml-report-plugin" + param("xmlReportParsing.reportType", "junit") + param("xmlReportParsing.reportDirs", dirs) + } +} + +fun ProjectFeatures.kibanaAgent(init: ProjectFeature.() -> Unit) { + feature { + type = "CloudImage" + param("network", kibanaConfiguration.agentNetwork) + param("subnet", kibanaConfiguration.agentSubnet) + param("growingId", "true") + param("agent_pool_id", "-2") + param("preemptible", "false") + param("sourceProject", "elastic-images-prod") + param("sourceImageFamily", "elastic-kibana-ci-ubuntu-1804-lts") + param("zone", "us-central1-a") + param("profileId", "kibana") + param("diskType", "pd-ssd") + param("machineCustom", "false") + param("maxInstances", "200") + param("imageType", "ImageFamily") + param("diskSizeGb", "75") // TODO + init() + } +} + +fun ProjectFeatures.kibanaAgent(size: String, init: ProjectFeature.() -> Unit = {}) { + kibanaAgent { + id = "KIBANA_STANDARD_$size" + param("source-id", "kibana-standard-$size-") + param("machineType", "n2-standard-$size") + init() + } +} + +fun BuildType.kibanaAgent(size: String) { + requirements { + startsWith("teamcity.agent.name", "kibana-standard-$size-", "RQ_AGENT_NAME") + } +} + +fun BuildType.kibanaAgent(size: Int) { + kibanaAgent(size.toString()) +} + +val testArtifactRules = """ + target/kibana-* + target/test-metrics/* + target/kibana-security-solution/**/*.png + target/junit/**/* + target/test-suites-ci-plan.json + test/**/screenshots/session/*.png + test/**/screenshots/failure/*.png + test/**/screenshots/diff/*.png + test/functional/failure_debug/html/*.html + x-pack/test/**/screenshots/session/*.png + x-pack/test/**/screenshots/failure/*.png + x-pack/test/**/screenshots/diff/*.png + x-pack/test/functional/failure_debug/html/*.html + x-pack/test/functional/apps/reporting/reports/session/*.pdf + """.trimIndent() + +fun BuildType.addTestSettings() { + artifactRules += "\n" + testArtifactRules + steps { + failedTestReporter() + } + features { + junit() + } +} + +fun BuildType.addSlackNotifications(to: String = "#kibana-teamcity-testing") { + params { + param("elastic.slack.enabled", "true") + param("elastic.slack.channels", to) + } +} + +fun BuildType.dependsOn(buildType: BuildType, init: SnapshotDependency.() -> Unit = {}) { + dependencies { + snapshot(buildType) { + reuseBuilds = ReuseBuilds.SUCCESSFUL + onDependencyCancel = FailureAction.ADD_PROBLEM + onDependencyFailure = FailureAction.ADD_PROBLEM + synchronizeRevisions = true + init() + } + } +} + +fun BuildType.dependsOn(vararg buildTypes: BuildType, init: SnapshotDependency.() -> Unit = {}) { + buildTypes.forEach { dependsOn(it, init) } +} + +fun BuildSteps.failedTestReporter(init: ScriptBuildStep.() -> Unit = {}) { + script { + name = "Failed Test Reporter" + scriptContent = + """ + #!/bin/bash + node scripts/report_failed_tests + """.trimIndent() + executionMode = BuildStep.ExecutionMode.RUN_ON_FAILURE + init() + } +} + +// Note: This is currently only used for tests and has a retry in it for flaky tests. +// The retry should be refactored if runbld is ever needed for other tasks. +fun BuildSteps.runbld(stepName: String, script: String) { + script { + name = stepName + + // The indentation for this string is like this to ensure 100% that the RUNBLD-SCRIPT heredoc termination will not have spaces at the beginning + scriptContent = +"""#!/bin/bash + +set -euo pipefail + +source .ci/teamcity/util.sh + +branchName="${'$'}GIT_BRANCH" +branchName="${'$'}{branchName#refs\/heads\/}" + +if [[ "${'$'}{GITHUB_PR_NUMBER-}" ]]; then + branchName=pull-request +fi + +project=kibana +if [[ "${'$'}{ES_SNAPSHOT_MANIFEST-}" ]]; then + project=kibana-es-snapshot-verify +fi + +# These parameters are only for runbld reporting +export JENKINS_HOME="${'$'}HOME" +export BUILD_URL="%teamcity.serverUrl%/build/%teamcity.build.id%" +export branch_specifier=${'$'}branchName +export NODE_LABELS='teamcity' +export BUILD_NUMBER="%build.number%" +export EXECUTOR_NUMBER='' +export NODE_NAME='' + +export OLD_PATH="${'$'}PATH" + +file=${'$'}(mktemp) + +( +cat < ${'$'}file + +tc_retry /usr/local/bin/runbld -d "${'$'}(pwd)" --job-name="elastic+${'$'}project+${'$'}branchName" ${'$'}file +""" + } +} diff --git a/.teamcity/src/builds/BaselineCi.kt b/.teamcity/src/builds/BaselineCi.kt new file mode 100644 index 000000000000..ae316960acf8 --- /dev/null +++ b/.teamcity/src/builds/BaselineCi.kt @@ -0,0 +1,38 @@ +package builds + +import addSlackNotifications +import builds.default.DefaultBuild +import builds.default.DefaultSavedObjectFieldMetrics +import builds.oss.OssBuild +import dependsOn +import jetbrains.buildServer.configs.kotlin.v2019_2.BuildType +import jetbrains.buildServer.configs.kotlin.v2019_2.FailureAction +import jetbrains.buildServer.configs.kotlin.v2019_2.triggers.vcs +import templates.KibanaTemplate + +object BaselineCi : BuildType({ + id("Baseline_CI") + name = "Baseline CI" + description = "Runs builds, saved object field metrics for every commit" + type = Type.COMPOSITE + paused = true + + templates(KibanaTemplate) + + triggers { + vcs { + branchFilter = "refs/heads/master_teamcity" +// perCheckinTriggering = true // TODO re-enable this later, it wreaks havoc when I merge upstream + } + } + + dependsOn( + OssBuild, + DefaultBuild, + DefaultSavedObjectFieldMetrics + ) { + onDependencyCancel = FailureAction.ADD_PROBLEM + } + + addSlackNotifications() +}) diff --git a/.teamcity/src/builds/Checks.kt b/.teamcity/src/builds/Checks.kt new file mode 100644 index 000000000000..1228ea4d94f4 --- /dev/null +++ b/.teamcity/src/builds/Checks.kt @@ -0,0 +1,37 @@ +package builds + +import jetbrains.buildServer.configs.kotlin.v2019_2.BuildType +import jetbrains.buildServer.configs.kotlin.v2019_2.buildSteps.script +import kibanaAgent + +object Checks : BuildType({ + name = "Checks" + description = "Executes Various Checks" + + kibanaAgent(4) + + val checkScripts = mapOf( + "Check Telemetry Schema" to ".ci/teamcity/checks/telemetry.sh", + "Check TypeScript Projects" to ".ci/teamcity/checks/ts_projects.sh", + "Check File Casing" to ".ci/teamcity/checks/file_casing.sh", + "Check Licenses" to ".ci/teamcity/checks/licenses.sh", + "Verify NOTICE" to ".ci/teamcity/checks/verify_notice.sh", + "Test Hardening" to ".ci/teamcity/checks/test_hardening.sh", + "Check Types" to ".ci/teamcity/checks/type_check.sh", + "Check Doc API Changes" to ".ci/teamcity/checks/doc_api_changes.sh", + "Check Bundle Limits" to ".ci/teamcity/checks/bundle_limits.sh", + "Check i18n" to ".ci/teamcity/checks/i18n.sh" + ) + + steps { + for (checkScript in checkScripts) { + script { + name = checkScript.key + scriptContent = """ + #!/bin/bash + ${checkScript.value} + """.trimIndent() + } + } + } +}) diff --git a/.teamcity/src/builds/FullCi.kt b/.teamcity/src/builds/FullCi.kt new file mode 100644 index 000000000000..7f19304428d7 --- /dev/null +++ b/.teamcity/src/builds/FullCi.kt @@ -0,0 +1,30 @@ +package builds + +import builds.default.* +import builds.oss.* +import builds.test.AllTests +import dependsOn +import jetbrains.buildServer.configs.kotlin.v2019_2.BuildType + +object FullCi : BuildType({ + id("Full_CI") + name = "Full CI" + description = "Runs everything in CI. For tracked branches and PRs." + type = Type.COMPOSITE + + dependsOn( + Lint, + Checks, + AllTests, + OssBuild, + OssAccessibility, + OssPluginFunctional, + OssCiGroups, + OssFirefox, + DefaultBuild, + DefaultCiGroups, + DefaultFirefox, + DefaultAccessibility, + DefaultSecuritySolution + ) +}) diff --git a/.teamcity/src/builds/HourlyCi.kt b/.teamcity/src/builds/HourlyCi.kt new file mode 100644 index 000000000000..605a22f01297 --- /dev/null +++ b/.teamcity/src/builds/HourlyCi.kt @@ -0,0 +1,34 @@ +package builds + +import addSlackNotifications +import dependsOn +import jetbrains.buildServer.configs.kotlin.v2019_2.BuildType +import jetbrains.buildServer.configs.kotlin.v2019_2.FailureAction +import jetbrains.buildServer.configs.kotlin.v2019_2.triggers.schedule + +object HourlyCi : BuildType({ + id("Hourly_CI") + name = "Hourly CI" + description = "Runs everything in CI, hourly" + type = Type.COMPOSITE + + triggers { + schedule { + schedulingPolicy = cron { + hours = "*" + minutes = "0" + } + branchFilter = "refs/heads/master_teamcity" + triggerBuild = always() + withPendingChangesOnly = true + } + } + + dependsOn( + FullCi + ) { + onDependencyCancel = FailureAction.ADD_PROBLEM + } + + addSlackNotifications() +}) diff --git a/.teamcity/src/builds/Lint.kt b/.teamcity/src/builds/Lint.kt new file mode 100644 index 000000000000..0b3b3b013b5e --- /dev/null +++ b/.teamcity/src/builds/Lint.kt @@ -0,0 +1,33 @@ +package builds + +import jetbrains.buildServer.configs.kotlin.v2019_2.BuildType +import jetbrains.buildServer.configs.kotlin.v2019_2.buildSteps.script +import kibanaAgent + +object Lint : BuildType({ + name = "Lint" + description = "Executes Linting, such as eslint and sasslint" + + kibanaAgent(2) + + steps { + script { + name = "Sasslint" + + scriptContent = + """ + #!/bin/bash + yarn run grunt run:sasslint + """.trimIndent() + } + + script { + name = "ESLint" + scriptContent = + """ + #!/bin/bash + yarn run grunt run:eslint + """.trimIndent() + } + } +}) diff --git a/.teamcity/src/builds/PullRequestCi.kt b/.teamcity/src/builds/PullRequestCi.kt new file mode 100644 index 000000000000..d3eb697981ce --- /dev/null +++ b/.teamcity/src/builds/PullRequestCi.kt @@ -0,0 +1,78 @@ +package builds + +import dependsOn +import jetbrains.buildServer.configs.kotlin.v2019_2.AbsoluteId +import jetbrains.buildServer.configs.kotlin.v2019_2.BuildType +import jetbrains.buildServer.configs.kotlin.v2019_2.buildFeatures.PullRequests +import jetbrains.buildServer.configs.kotlin.v2019_2.buildFeatures.commitStatusPublisher +import jetbrains.buildServer.configs.kotlin.v2019_2.buildFeatures.pullRequests +import vcs.Kibana + +object PullRequestCi : BuildType({ + id = AbsoluteId("Kibana_PullRequest_CI") + name = "Pull Request CI" + type = Type.COMPOSITE + + buildNumberPattern = "%build.counter%-%env.GITHUB_PR_OWNER%-%env.GITHUB_PR_BRANCH%" + + vcs { + root(Kibana) + checkoutDir = "kibana" + + branchFilter = "+:pull/*" + excludeDefaultBranchChanges = true + } + + val prAllowedList = listOf( + "brianseeders", + "alexwizp", + "barlowm", + "DziyanaDzeraviankina", + "maryia-lapata", + "renovate[bot]", + "sulemanof", + "VladLasitsa" + ) + + params { + param("elastic.pull_request.enabled", "true") + param("elastic.pull_request.target_branch", "master_teamcity") + param("elastic.pull_request.allow_org_users", "true") + param("elastic.pull_request.allowed_repo_permissions", "admin,write") + param("elastic.pull_request.allowed_list", prAllowedList.joinToString(",")) + param("elastic.pull_request.cancel_in_progress_builds_on_update", "true") + + // These params should get filled in by the app that triggers builds + param("env.GITHUB_PR_TARGET_BRANCH", "") + param("env.GITHUB_PR_NUMBER", "") + param("env.GITHUB_PR_OWNER", "") + param("env.GITHUB_PR_REPO", "") + param("env.GITHUB_PR_BRANCH", "") + param("env.GITHUB_PR_TRIGGERED_SHA", "") + param("env.GITHUB_PR_LABELS", "") + param("env.GITHUB_PR_TRIGGER_COMMENT", "") + + param("reverse.dep.*.env.GITHUB_PR_TARGET_BRANCH", "") + param("reverse.dep.*.env.GITHUB_PR_NUMBER", "") + param("reverse.dep.*.env.GITHUB_PR_OWNER", "") + param("reverse.dep.*.env.GITHUB_PR_REPO", "") + param("reverse.dep.*.env.GITHUB_PR_BRANCH", "") + param("reverse.dep.*.env.GITHUB_PR_TRIGGERED_SHA", "") + param("reverse.dep.*.env.GITHUB_PR_LABELS", "") + param("reverse.dep.*.env.GITHUB_PR_TRIGGER_COMMENT", "") + } + + features { + commitStatusPublisher { + vcsRootExtId = "${Kibana.id}" + publisher = github { + githubUrl = "https://api.github.com" + authType = personalToken { + token = "credentialsJSON:07d22002-12de-4627-91c3-672bdb23b55b" + } + } + } + } + + dependsOn(FullCi) +}) diff --git a/.teamcity/src/builds/default/DefaultAccessibility.kt b/.teamcity/src/builds/default/DefaultAccessibility.kt new file mode 100755 index 000000000000..f0a9c60cf3e4 --- /dev/null +++ b/.teamcity/src/builds/default/DefaultAccessibility.kt @@ -0,0 +1,12 @@ +package builds.default + +import runbld + +object DefaultAccessibility : DefaultFunctionalBase({ + id("DefaultAccessibility") + name = "Accessibility" + + steps { + runbld("Default Accessibility", "./.ci/teamcity/default/accessibility.sh") + } +}) diff --git a/.teamcity/src/builds/default/DefaultBuild.kt b/.teamcity/src/builds/default/DefaultBuild.kt new file mode 100644 index 000000000000..f4683e6cf0c1 --- /dev/null +++ b/.teamcity/src/builds/default/DefaultBuild.kt @@ -0,0 +1,56 @@ +package builds.default + +import jetbrains.buildServer.configs.kotlin.v2019_2.BuildType +import jetbrains.buildServer.configs.kotlin.v2019_2.Dependencies +import jetbrains.buildServer.configs.kotlin.v2019_2.FailureAction +import jetbrains.buildServer.configs.kotlin.v2019_2.buildSteps.script + +object DefaultBuild : BuildType({ + name = "Build Default" + description = "Generates Default Build Distribution artifact" + + artifactRules = """ + +:install/kibana/**/* => kibana-default.tar.gz + target/kibana-* + +:src/**/target/public/**/* => kibana-default-plugins.tar.gz!/src/ + +:x-pack/plugins/**/target/public/**/* => kibana-default-plugins.tar.gz!/x-pack/plugins/ + +:x-pack/test/**/target/public/**/* => kibana-default-plugins.tar.gz!/x-pack/test/ + +:examples/**/target/public/**/* => kibana-default-plugins.tar.gz!/examples/ + +:test/**/target/public/**/* => kibana-default-plugins.tar.gz!/test/ + """.trimIndent() + + requirements { + startsWith("teamcity.agent.name", "kibana-c2-16-", "RQ_AGENT_NAME") + } + + steps { + script { + name = "Build Default Distribution" + scriptContent = + """ + #!/bin/bash + ./.ci/teamcity/default/build.sh + """.trimIndent() + } + } +}) + +fun Dependencies.defaultBuild(rules: String = "+:kibana-default.tar.gz!** => ../build/kibana-build-default") { + dependency(DefaultBuild) { + snapshot { + onDependencyFailure = FailureAction.FAIL_TO_START + onDependencyCancel = FailureAction.FAIL_TO_START + } + + artifacts { + artifactRules = rules + } + } +} + +fun Dependencies.defaultBuildWithPlugins() { + defaultBuild(""" + +:kibana-default.tar.gz!** => ../build/kibana-build-default + +:kibana-default-plugins.tar.gz!** + """.trimIndent()) +} diff --git a/.teamcity/src/builds/default/DefaultCiGroup.kt b/.teamcity/src/builds/default/DefaultCiGroup.kt new file mode 100755 index 000000000000..7dbe9cd0ba84 --- /dev/null +++ b/.teamcity/src/builds/default/DefaultCiGroup.kt @@ -0,0 +1,15 @@ +package builds.default + +import jetbrains.buildServer.configs.kotlin.v2019_2.* +import runbld + +class DefaultCiGroup(val ciGroup: Int = 0, init: BuildType.() -> Unit = {}) : DefaultFunctionalBase({ + id("DefaultCiGroup_$ciGroup") + name = "CI Group $ciGroup" + + steps { + runbld("Default CI Group $ciGroup", "./.ci/teamcity/default/ci_group.sh $ciGroup") + } + + init() +}) diff --git a/.teamcity/src/builds/default/DefaultCiGroups.kt b/.teamcity/src/builds/default/DefaultCiGroups.kt new file mode 100644 index 000000000000..6f1d45598c92 --- /dev/null +++ b/.teamcity/src/builds/default/DefaultCiGroups.kt @@ -0,0 +1,15 @@ +package builds.default + +import dependsOn +import jetbrains.buildServer.configs.kotlin.v2019_2.BuildType + +const val DEFAULT_CI_GROUP_COUNT = 10 +val defaultCiGroups = (1..DEFAULT_CI_GROUP_COUNT).map { DefaultCiGroup(it) } + +object DefaultCiGroups : BuildType({ + id("Default_CIGroups_Composite") + name = "CI Groups" + type = Type.COMPOSITE + + dependsOn(*defaultCiGroups.toTypedArray()) +}) diff --git a/.teamcity/src/builds/default/DefaultFirefox.kt b/.teamcity/src/builds/default/DefaultFirefox.kt new file mode 100755 index 000000000000..2429967d2493 --- /dev/null +++ b/.teamcity/src/builds/default/DefaultFirefox.kt @@ -0,0 +1,12 @@ +package builds.default + +import runbld + +object DefaultFirefox : DefaultFunctionalBase({ + id("DefaultFirefox") + name = "Firefox" + + steps { + runbld("Default Firefox", "./.ci/teamcity/default/firefox.sh") + } +}) diff --git a/.teamcity/src/builds/default/DefaultFunctionalBase.kt b/.teamcity/src/builds/default/DefaultFunctionalBase.kt new file mode 100644 index 000000000000..d8124bd8521c --- /dev/null +++ b/.teamcity/src/builds/default/DefaultFunctionalBase.kt @@ -0,0 +1,19 @@ +package builds.default + +import addTestSettings +import jetbrains.buildServer.configs.kotlin.v2019_2.BuildType + +open class DefaultFunctionalBase(init: BuildType.() -> Unit = {}) : BuildType({ + params { + param("env.KBN_NP_PLUGINS_BUILT", "true") + } + + dependencies { + defaultBuildWithPlugins() + } + + init() + + addTestSettings() +}) + diff --git a/.teamcity/src/builds/default/DefaultSavedObjectFieldMetrics.kt b/.teamcity/src/builds/default/DefaultSavedObjectFieldMetrics.kt new file mode 100644 index 000000000000..61505d4757fa --- /dev/null +++ b/.teamcity/src/builds/default/DefaultSavedObjectFieldMetrics.kt @@ -0,0 +1,28 @@ +package builds.default + +import jetbrains.buildServer.configs.kotlin.v2019_2.* +import jetbrains.buildServer.configs.kotlin.v2019_2.buildSteps.script + +object DefaultSavedObjectFieldMetrics : BuildType({ + id("DefaultSavedObjectFieldMetrics") + name = "Default Saved Object Field Metrics" + + params { + param("env.KBN_NP_PLUGINS_BUILT", "true") + } + + steps { + script { + name = "Default Saved Object Field Metrics" + scriptContent = + """ + #!/bin/bash + ./.ci/teamcity/default/saved_object_field_metrics.sh + """.trimIndent() + } + } + + dependencies { + defaultBuild() + } +}) diff --git a/.teamcity/src/builds/default/DefaultSecuritySolution.kt b/.teamcity/src/builds/default/DefaultSecuritySolution.kt new file mode 100755 index 000000000000..1c3b85257c28 --- /dev/null +++ b/.teamcity/src/builds/default/DefaultSecuritySolution.kt @@ -0,0 +1,15 @@ +package builds.default + +import addTestSettings +import runbld + +object DefaultSecuritySolution : DefaultFunctionalBase({ + id("DefaultSecuritySolution") + name = "Security Solution" + + steps { + runbld("Default Security Solution", "./.ci/teamcity/default/security_solution.sh") + } + + addTestSettings() +}) diff --git a/.teamcity/src/builds/es_snapshots/Build.kt b/.teamcity/src/builds/es_snapshots/Build.kt new file mode 100644 index 000000000000..d0c849ff5f99 --- /dev/null +++ b/.teamcity/src/builds/es_snapshots/Build.kt @@ -0,0 +1,84 @@ +package builds.es_snapshots + +import addSlackNotifications +import jetbrains.buildServer.configs.kotlin.v2019_2.* +import jetbrains.buildServer.configs.kotlin.v2019_2.buildSteps.script +import vcs.Elasticsearch +import vcs.Kibana + +object ESSnapshotBuild : BuildType({ + name = "Build Snapshot" + paused = true + + requirements { + startsWith("teamcity.agent.name", "kibana-c2-16-", "RQ_AGENT_NAME") + } + + vcs { + root(Kibana, "+:. => kibana") + root(Elasticsearch, "+:. => elasticsearch") + checkoutDir = "" + } + + params { + param("env.ELASTICSEARCH_BRANCH", "%vcsroot.${Elasticsearch.id.toString()}.branch%") + param("env.ELASTICSEARCH_GIT_COMMIT", "%build.vcs.number.${Elasticsearch.id.toString()}%") + + param("env.GOOGLE_APPLICATION_CREDENTIALS", "%teamcity.build.workingDir%/gcp-credentials.json") + password("env.GOOGLE_APPLICATION_CREDENTIALS_JSON", "credentialsJSON:6e0acb7c-f89c-4225-84b8-4fc102f1a5ef", display = ParameterDisplay.HIDDEN) + } + + steps { + script { + name = "Setup Environment" + scriptContent = + """ + #!/bin/bash + cd kibana + ./.ci/teamcity/setup_env.sh + """.trimIndent() + } + + script { + name = "Setup Node and Yarn" + scriptContent = + """ + #!/bin/bash + cd kibana + ./.ci/teamcity/setup_node.sh + """.trimIndent() + } + + script { + name = "Build Elasticsearch Distribution" + scriptContent = + """ + #!/bin/bash + cd kibana + ./.ci/teamcity/es_snapshots/build.sh + """.trimIndent() + } + + script { + name = "Setup Google Cloud Credentials" + scriptContent = + """#!/bin/bash + echo "${"$"}GOOGLE_APPLICATION_CREDENTIALS_JSON" > "${"$"}GOOGLE_APPLICATION_CREDENTIALS" + gcloud auth activate-service-account --key-file "${"$"}GOOGLE_APPLICATION_CREDENTIALS" + """.trimIndent() + } + + script { + name = "Create Snapshot Manifest" + scriptContent = + """#!/bin/bash + cd kibana + node ./.ci/teamcity/es_snapshots/create_manifest.js "$(cd ../es-build && pwd)" + """.trimIndent() + } + } + + artifactRules = "+:es-build/**/*.json" + + addSlackNotifications() +}) diff --git a/.teamcity/src/builds/es_snapshots/Promote.kt b/.teamcity/src/builds/es_snapshots/Promote.kt new file mode 100644 index 000000000000..9303439d49f3 --- /dev/null +++ b/.teamcity/src/builds/es_snapshots/Promote.kt @@ -0,0 +1,87 @@ +package builds.es_snapshots + +import addSlackNotifications +import jetbrains.buildServer.configs.kotlin.v2019_2.* +import jetbrains.buildServer.configs.kotlin.v2019_2.buildSteps.script +import jetbrains.buildServer.configs.kotlin.v2019_2.triggers.finishBuildTrigger +import vcs.Kibana + +object ESSnapshotPromote : BuildType({ + name = "Promote Snapshot" + paused = true + type = Type.DEPLOYMENT + + vcs { + root(Kibana, "+:. => kibana") + checkoutDir = "" + } + + params { + param("env.ES_SNAPSHOT_MANIFEST", "${ESSnapshotBuild.depParamRefs["env.ES_SNAPSHOT_MANIFEST"]}") + param("env.GOOGLE_APPLICATION_CREDENTIALS", "%teamcity.build.workingDir%/gcp-credentials.json") + password("env.GOOGLE_APPLICATION_CREDENTIALS_JSON", "credentialsJSON:6e0acb7c-f89c-4225-84b8-4fc102f1a5ef", display = ParameterDisplay.HIDDEN) + } + + triggers { + finishBuildTrigger { + buildType = Verify.id.toString() + successfulOnly = true + } + } + + steps { + script { + name = "Setup Environment" + scriptContent = + """ + #!/bin/bash + cd kibana + ./.ci/teamcity/setup_env.sh + """.trimIndent() + } + + script { + name = "Setup Node and Yarn" + scriptContent = + """ + #!/bin/bash + cd kibana + ./.ci/teamcity/setup_node.sh + """.trimIndent() + } + + script { + name = "Setup Google Cloud Credentials" + scriptContent = + """#!/bin/bash + echo "${"$"}GOOGLE_APPLICATION_CREDENTIALS_JSON" > "${"$"}GOOGLE_APPLICATION_CREDENTIALS" + gcloud auth activate-service-account --key-file "${"$"}GOOGLE_APPLICATION_CREDENTIALS" + """.trimIndent() + } + + script { + name = "Promote Snapshot Manifest" + scriptContent = + """#!/bin/bash + cd kibana + node ./.ci/teamcity/es_snapshots/promote_manifest.js "${"$"}ES_SNAPSHOT_MANIFEST" + """.trimIndent() + } + } + + dependencies { + dependency(ESSnapshotBuild) { + snapshot { } + + // This is just here to allow build selection in the UI, the file isn't actually used + artifacts { + artifactRules = "manifest.json" + } + } + dependency(Verify) { + snapshot { } + } + } + + addSlackNotifications() +}) diff --git a/.teamcity/src/builds/es_snapshots/PromoteImmediate.kt b/.teamcity/src/builds/es_snapshots/PromoteImmediate.kt new file mode 100644 index 000000000000..f80a97873b24 --- /dev/null +++ b/.teamcity/src/builds/es_snapshots/PromoteImmediate.kt @@ -0,0 +1,79 @@ +package builds.es_snapshots + +import addSlackNotifications +import jetbrains.buildServer.configs.kotlin.v2019_2.* +import jetbrains.buildServer.configs.kotlin.v2019_2.buildSteps.script +import jetbrains.buildServer.configs.kotlin.v2019_2.triggers.finishBuildTrigger +import vcs.Elasticsearch +import vcs.Kibana + +object ESSnapshotPromoteImmediate : BuildType({ + name = "Immediately Promote Snapshot" + description = "Skip testing and immediately promote the selected snapshot" + paused = true + type = Type.DEPLOYMENT + + vcs { + root(Kibana, "+:. => kibana") + checkoutDir = "" + } + + params { + param("env.ES_SNAPSHOT_MANIFEST", "${ESSnapshotBuild.depParamRefs["env.ES_SNAPSHOT_MANIFEST"]}") + param("env.GOOGLE_APPLICATION_CREDENTIALS", "%teamcity.build.workingDir%/gcp-credentials.json") + password("env.GOOGLE_APPLICATION_CREDENTIALS_JSON", "credentialsJSON:6e0acb7c-f89c-4225-84b8-4fc102f1a5ef", display = ParameterDisplay.HIDDEN) + } + + steps { + script { + name = "Setup Environment" + scriptContent = + """ + #!/bin/bash + cd kibana + ./.ci/teamcity/setup_env.sh + """.trimIndent() + } + + script { + name = "Setup Node and Yarn" + scriptContent = + """ + #!/bin/bash + cd kibana + ./.ci/teamcity/setup_node.sh + """.trimIndent() + } + + script { + name = "Setup Google Cloud Credentials" + scriptContent = + """#!/bin/bash + echo "${"$"}GOOGLE_APPLICATION_CREDENTIALS_JSON" > "${"$"}GOOGLE_APPLICATION_CREDENTIALS" + gcloud auth activate-service-account --key-file "${"$"}GOOGLE_APPLICATION_CREDENTIALS" + """.trimIndent() + } + + script { + name = "Promote Snapshot Manifest" + scriptContent = + """#!/bin/bash + cd kibana + node ./.ci/teamcity/es_snapshots/promote_manifest.js "${"$"}ES_SNAPSHOT_MANIFEST" + """.trimIndent() + } + } + + dependencies { + dependency(ESSnapshotBuild) { + snapshot { } + + // This is just here to allow build selection in the UI, the file isn't actually used + artifacts { + artifactRules = "manifest.json" + } + } + } + + addSlackNotifications() +}) diff --git a/.teamcity/src/builds/es_snapshots/Verify.kt b/.teamcity/src/builds/es_snapshots/Verify.kt new file mode 100644 index 000000000000..c778814af536 --- /dev/null +++ b/.teamcity/src/builds/es_snapshots/Verify.kt @@ -0,0 +1,96 @@ +package builds.es_snapshots + +import builds.default.DefaultBuild +import builds.default.DefaultSecuritySolution +import builds.default.defaultCiGroups +import builds.oss.OssBuild +import builds.oss.OssPluginFunctional +import builds.oss.ossCiGroups +import builds.test.ApiServerIntegration +import builds.test.JestIntegration +import dependsOn +import jetbrains.buildServer.configs.kotlin.v2019_2.* + +val cloneForVerify = { build: BuildType -> + val newBuild = BuildType() + build.copyTo(newBuild) + newBuild.id = AbsoluteId(build.id?.toString() + "_ES_Snapshots") + newBuild.params { + param("env.ES_SNAPSHOT_MANIFEST", "${ESSnapshotBuild.depParamRefs["env.ES_SNAPSHOT_MANIFEST"]}") + } + newBuild.dependencies { + dependency(ESSnapshotBuild) { + snapshot { + onDependencyFailure = FailureAction.FAIL_TO_START + onDependencyCancel = FailureAction.FAIL_TO_START + } + // This is just here to allow us to select a build when manually triggering a build using the UI + artifacts { + artifactRules = "manifest.json" + } + } + } + newBuild.steps.items.removeIf { it.name == "Failed Test Reporter" } + newBuild +} + +val ossBuildsToClone = listOf( + *ossCiGroups.toTypedArray(), + OssPluginFunctional +) + +val ossCloned = ossBuildsToClone.map { cloneForVerify(it) } + +val defaultBuildsToClone = listOf( + *defaultCiGroups.toTypedArray(), + DefaultSecuritySolution +) + +val defaultCloned = defaultBuildsToClone.map { cloneForVerify(it) } + +val integrationsBuildsToClone = listOf( + ApiServerIntegration, + JestIntegration +) + +val integrationCloned = integrationsBuildsToClone.map { cloneForVerify(it) } + +object OssTests : BuildType({ + id("ES_Snapshots_OSS_Tests_Composite") + name = "OSS Distro Tests" + type = Type.COMPOSITE + + dependsOn(*ossCloned.toTypedArray()) +}) + +object DefaultTests : BuildType({ + id("ES_Snapshots_Default_Tests_Composite") + name = "Default Distro Tests" + type = Type.COMPOSITE + + dependsOn(*defaultCloned.toTypedArray()) +}) + +object IntegrationTests : BuildType({ + id("ES_Snapshots_Integration_Tests_Composite") + name = "Integration Tests" + type = Type.COMPOSITE + + dependsOn(*integrationCloned.toTypedArray()) +}) + +object Verify : BuildType({ + id("ES_Snapshots_Verify_Composite") + name = "Verify Snapshot" + description = "Run all Kibana functional and integration tests using a given Elasticsearch snapshot" + type = Type.COMPOSITE + + dependsOn( + ESSnapshotBuild, + OssBuild, + DefaultBuild, + OssTests, + DefaultTests, + IntegrationTests + ) +}) diff --git a/.teamcity/src/builds/oss/OssAccessibility.kt b/.teamcity/src/builds/oss/OssAccessibility.kt new file mode 100644 index 000000000000..8e4a7acd77b7 --- /dev/null +++ b/.teamcity/src/builds/oss/OssAccessibility.kt @@ -0,0 +1,13 @@ +package builds.oss + +import jetbrains.buildServer.configs.kotlin.v2019_2.buildSteps.script +import runbld + +object OssAccessibility : OssFunctionalBase({ + id("OssAccessibility") + name = "Accessibility" + + steps { + runbld("OSS Accessibility", "./.ci/teamcity/oss/accessibility.sh") + } +}) diff --git a/.teamcity/src/builds/oss/OssBuild.kt b/.teamcity/src/builds/oss/OssBuild.kt new file mode 100644 index 000000000000..50fd73c17ba4 --- /dev/null +++ b/.teamcity/src/builds/oss/OssBuild.kt @@ -0,0 +1,41 @@ +package builds.oss + +import jetbrains.buildServer.configs.kotlin.v2019_2.BuildType +import jetbrains.buildServer.configs.kotlin.v2019_2.Dependencies +import jetbrains.buildServer.configs.kotlin.v2019_2.FailureAction +import jetbrains.buildServer.configs.kotlin.v2019_2.buildSteps.script + +object OssBuild : BuildType({ + name = "Build OSS" + description = "Generates OSS Build Distribution artifact" + + requirements { + startsWith("teamcity.agent.name", "kibana-c2-16-", "RQ_AGENT_NAME") + } + + steps { + script { + name = "Build OSS Distribution" + scriptContent = + """ + #!/bin/bash + ./.ci/teamcity/oss/build.sh + """.trimIndent() + } + } + + artifactRules = "+:build/oss/kibana-build-oss/**/* => kibana-oss.tar.gz" +}) + +fun Dependencies.ossBuild(rules: String = "+:kibana-oss.tar.gz!** => ../build/kibana-build-oss") { + dependency(OssBuild) { + snapshot { + onDependencyFailure = FailureAction.FAIL_TO_START + onDependencyCancel = FailureAction.FAIL_TO_START + } + + artifacts { + artifactRules = rules + } + } +} diff --git a/.teamcity/src/builds/oss/OssCiGroup.kt b/.teamcity/src/builds/oss/OssCiGroup.kt new file mode 100644 index 000000000000..1c188cd4c175 --- /dev/null +++ b/.teamcity/src/builds/oss/OssCiGroup.kt @@ -0,0 +1,15 @@ +package builds.oss + +import jetbrains.buildServer.configs.kotlin.v2019_2.* +import runbld + +class OssCiGroup(val ciGroup: Int, init: BuildType.() -> Unit = {}) : OssFunctionalBase({ + id("OssCiGroup_$ciGroup") + name = "CI Group $ciGroup" + + steps { + runbld("OSS CI Group $ciGroup", "./.ci/teamcity/oss/ci_group.sh $ciGroup") + } + + init() +}) diff --git a/.teamcity/src/builds/oss/OssCiGroups.kt b/.teamcity/src/builds/oss/OssCiGroups.kt new file mode 100644 index 000000000000..931cca2554a2 --- /dev/null +++ b/.teamcity/src/builds/oss/OssCiGroups.kt @@ -0,0 +1,15 @@ +package builds.oss + +import dependsOn +import jetbrains.buildServer.configs.kotlin.v2019_2.BuildType + +const val OSS_CI_GROUP_COUNT = 12 +val ossCiGroups = (1..OSS_CI_GROUP_COUNT).map { OssCiGroup(it) } + +object OssCiGroups : BuildType({ + id("OSS_CIGroups_Composite") + name = "CI Groups" + type = Type.COMPOSITE + + dependsOn(*ossCiGroups.toTypedArray()) +}) diff --git a/.teamcity/src/builds/oss/OssFirefox.kt b/.teamcity/src/builds/oss/OssFirefox.kt new file mode 100644 index 000000000000..2db8314fa44f --- /dev/null +++ b/.teamcity/src/builds/oss/OssFirefox.kt @@ -0,0 +1,12 @@ +package builds.oss + +import runbld + +object OssFirefox : OssFunctionalBase({ + id("OssFirefox") + name = "Firefox" + + steps { + runbld("OSS Firefox", "./.ci/teamcity/oss/firefox.sh") + } +}) diff --git a/.teamcity/src/builds/oss/OssFunctionalBase.kt b/.teamcity/src/builds/oss/OssFunctionalBase.kt new file mode 100644 index 000000000000..d8189fd35896 --- /dev/null +++ b/.teamcity/src/builds/oss/OssFunctionalBase.kt @@ -0,0 +1,18 @@ +package builds.oss + +import addTestSettings +import jetbrains.buildServer.configs.kotlin.v2019_2.* + +open class OssFunctionalBase(init: BuildType.() -> Unit = {}) : BuildType({ + params { + param("env.KBN_NP_PLUGINS_BUILT", "true") + } + + dependencies { + ossBuild() + } + + init() + + addTestSettings() +}) diff --git a/.teamcity/src/builds/oss/OssPluginFunctional.kt b/.teamcity/src/builds/oss/OssPluginFunctional.kt new file mode 100644 index 000000000000..7fbf863820e4 --- /dev/null +++ b/.teamcity/src/builds/oss/OssPluginFunctional.kt @@ -0,0 +1,29 @@ +package builds.oss + +import addTestSettings +import jetbrains.buildServer.configs.kotlin.v2019_2.buildSteps.script +import runbld + +object OssPluginFunctional : OssFunctionalBase({ + id("OssPluginFunctional") + name = "Plugin Functional" + + steps { + script { + name = "Build OSS Plugins" + scriptContent = + """ + #!/bin/bash + ./.ci/teamcity/oss/build_plugins.sh + """.trimIndent() + } + + runbld("OSS Plugin Functional", "./.ci/teamcity/oss/plugin_functional.sh") + } + + dependencies { + ossBuild() + } + + addTestSettings() +}) diff --git a/.teamcity/src/builds/test/AllTests.kt b/.teamcity/src/builds/test/AllTests.kt new file mode 100644 index 000000000000..d1b5898d1a5f --- /dev/null +++ b/.teamcity/src/builds/test/AllTests.kt @@ -0,0 +1,12 @@ +package builds.test + +import dependsOn +import jetbrains.buildServer.configs.kotlin.v2019_2.BuildType + +object AllTests : BuildType({ + name = "All Tests" + description = "All Non-Functional Tests" + type = Type.COMPOSITE + + dependsOn(QuickTests, Jest, XPackJest, JestIntegration, ApiServerIntegration) +}) diff --git a/.teamcity/src/builds/test/ApiServerIntegration.kt b/.teamcity/src/builds/test/ApiServerIntegration.kt new file mode 100644 index 000000000000..d595840c879e --- /dev/null +++ b/.teamcity/src/builds/test/ApiServerIntegration.kt @@ -0,0 +1,17 @@ +package builds.test + +import addTestSettings +import jetbrains.buildServer.configs.kotlin.v2019_2.BuildType +import runbld + +object ApiServerIntegration : BuildType({ + name = "API/Server Integration" + description = "Executes API and Server Integration Tests" + + steps { + runbld("API Integration", "yarn run grunt run:apiIntegrationTests") + runbld("Server Integration", "yarn run grunt run:serverIntegrationTests") + } + + addTestSettings() +}) diff --git a/.teamcity/src/builds/test/Jest.kt b/.teamcity/src/builds/test/Jest.kt new file mode 100644 index 000000000000..04217a4e99b1 --- /dev/null +++ b/.teamcity/src/builds/test/Jest.kt @@ -0,0 +1,19 @@ +package builds.test + +import addTestSettings +import jetbrains.buildServer.configs.kotlin.v2019_2.BuildType +import kibanaAgent +import runbld + +object Jest : BuildType({ + name = "Jest Unit" + description = "Executes Jest Unit Tests" + + kibanaAgent(8) + + steps { + runbld("Jest Unit", "yarn run grunt run:test_jest") + } + + addTestSettings() +}) diff --git a/.teamcity/src/builds/test/JestIntegration.kt b/.teamcity/src/builds/test/JestIntegration.kt new file mode 100644 index 000000000000..9ec1360dcb1d --- /dev/null +++ b/.teamcity/src/builds/test/JestIntegration.kt @@ -0,0 +1,16 @@ +package builds.test + +import addTestSettings +import jetbrains.buildServer.configs.kotlin.v2019_2.BuildType +import runbld + +object JestIntegration : BuildType({ + name = "Jest Integration" + description = "Executes Jest Integration Tests" + + steps { + runbld("Jest Integration", "yarn run grunt run:test_jest_integration") + } + + addTestSettings() +}) diff --git a/.teamcity/src/builds/test/QuickTests.kt b/.teamcity/src/builds/test/QuickTests.kt new file mode 100644 index 000000000000..1fdb1e366e83 --- /dev/null +++ b/.teamcity/src/builds/test/QuickTests.kt @@ -0,0 +1,29 @@ +package builds.test + +import addTestSettings +import jetbrains.buildServer.configs.kotlin.v2019_2.BuildType +import kibanaAgent +import runbld + +object QuickTests : BuildType({ + name = "Quick Tests" + description = "Executes Quick Tests" + + kibanaAgent(2) + + val testScripts = mapOf( + "Test Hardening" to ".ci/teamcity/tests/test_hardening.sh", + "X-Pack List cyclic dependency" to ".ci/teamcity/tests/xpack_list_cyclic_dependency.sh", + "X-Pack SIEM cyclic dependency" to ".ci/teamcity/tests/xpack_siem_cyclic_dependency.sh", + "Test Projects" to ".ci/teamcity/tests/test_projects.sh", + "Mocha Tests" to ".ci/teamcity/tests/mocha.sh" + ) + + steps { + for (testScript in testScripts) { + runbld(testScript.key, testScript.value) + } + } + + addTestSettings() +}) diff --git a/.teamcity/src/builds/test/XPackJest.kt b/.teamcity/src/builds/test/XPackJest.kt new file mode 100644 index 000000000000..1958d39183ba --- /dev/null +++ b/.teamcity/src/builds/test/XPackJest.kt @@ -0,0 +1,22 @@ +package builds.test + +import addTestSettings +import jetbrains.buildServer.configs.kotlin.v2019_2.BuildType +import kibanaAgent +import runbld + +object XPackJest : BuildType({ + name = "X-Pack Jest Unit" + description = "Executes X-Pack Jest Unit Tests" + + kibanaAgent(16) + + steps { + runbld("X-Pack Jest Unit", """ + cd x-pack + node --max-old-space-size=6144 scripts/jest --ci --verbose --maxWorkers=6 + """.trimIndent()) + } + + addTestSettings() +}) diff --git a/.teamcity/src/projects/EsSnapshots.kt b/.teamcity/src/projects/EsSnapshots.kt new file mode 100644 index 000000000000..a5aa47d5cae4 --- /dev/null +++ b/.teamcity/src/projects/EsSnapshots.kt @@ -0,0 +1,55 @@ +package projects + +import builds.es_snapshots.* +import jetbrains.buildServer.configs.kotlin.v2019_2.* +import templates.KibanaTemplate + +object EsSnapshotsProject : Project({ + id("ES_Snapshots") + name = "ES Snapshots" + + subProject { + id("ES_Snapshot_Tests") + name = "Tests" + + defaultTemplate = KibanaTemplate + + subProject { + id("ES_Snapshot_Tests_OSS") + name = "OSS Distro Tests" + + ossCloned.forEach { + buildType(it) + } + + buildType(OssTests) + } + + subProject { + id("ES_Snapshot_Tests_Default") + name = "Default Distro Tests" + + defaultCloned.forEach { + buildType(it) + } + + buildType(DefaultTests) + } + + subProject { + id("ES_Snapshot_Tests_Integration") + name = "Integration Tests" + + integrationCloned.forEach { + buildType(it) + } + + buildType(IntegrationTests) + } + } + + buildType(ESSnapshotBuild) + buildType(ESSnapshotPromote) + buildType(ESSnapshotPromoteImmediate) + buildType(Verify) +}) diff --git a/.teamcity/src/projects/Kibana.kt b/.teamcity/src/projects/Kibana.kt new file mode 100644 index 000000000000..20c30eedf5b9 --- /dev/null +++ b/.teamcity/src/projects/Kibana.kt @@ -0,0 +1,171 @@ +package projects + +import vcs.Kibana +import builds.* +import builds.default.* +import builds.oss.* +import builds.test.* +import jetbrains.buildServer.configs.kotlin.v2019_2.* +import jetbrains.buildServer.configs.kotlin.v2019_2.projectFeatures.slackConnection +import kibanaAgent +import templates.KibanaTemplate +import templates.DefaultTemplate +import vcs.Elasticsearch + +class KibanaConfiguration() { + var agentNetwork: String = "teamcity" + var agentSubnet: String = "teamcity" + + constructor(init: KibanaConfiguration.() -> Unit) : this() { + init() + } +} + +var kibanaConfiguration = KibanaConfiguration() + +fun Kibana(config: KibanaConfiguration = KibanaConfiguration()) : Project { + kibanaConfiguration = config + + return Project { + params { + param("teamcity.ui.settings.readOnly", "true") + + // https://github.com/JetBrains/teamcity-webhooks + param("teamcity.internal.webhooks.enable", "true") + param("teamcity.internal.webhooks.events", "BUILD_STARTED;BUILD_FINISHED;BUILD_INTERRUPTED;CHANGES_LOADED;BUILD_TYPE_ADDED_TO_QUEUE;BUILD_PROBLEMS_CHANGED") + param("teamcity.internal.webhooks.url", "https://ci-stats.kibana.dev/_teamcity_webhook") + param("teamcity.internal.webhooks.username", "automation") + password("teamcity.internal.webhooks.password", "credentialsJSON:b2ee34c5-fc89-4596-9b47-ecdeb68e4e7a", display = ParameterDisplay.HIDDEN) + } + + vcsRoot(Kibana) + vcsRoot(Elasticsearch) + + template(DefaultTemplate) + template(KibanaTemplate) + + defaultTemplate = DefaultTemplate + + features { + val sizes = listOf("2", "4", "8", "16") + for (size in sizes) { + kibanaAgent(size) + } + + kibanaAgent { + id = "KIBANA_C2_16" + param("source-id", "kibana-c2-16-") + param("machineType", "c2-standard-16") + } + + feature { + id = "kibana" + type = "CloudProfile" + param("agentPushPreset", "") + param("profileId", "kibana") + param("profileServerUrl", "") + param("name", "kibana") + param("total-work-time", "") + param("credentialsType", "key") + param("description", "") + param("next-hour", "") + param("cloud-code", "google") + param("terminate-after-build", "true") + param("terminate-idle-time", "30") + param("enabled", "true") + param("secure:accessKey", "credentialsJSON:447fdd4d-7129-46b7-9822-2e57658c7422") + } + + slackConnection { + id = "KIBANA_SLACK" + displayName = "Kibana Slack" + botToken = "credentialsJSON:39eafcfc-97a6-4877-82c1-115f1f10d14b" + clientId = "12985172978.1291178427153" + clientSecret = "credentialsJSON:8b5901fb-fd2c-4e45-8aff-fdd86dc68f67" + } + } + + subProject { + id("CI") + name = "CI" + defaultTemplate = KibanaTemplate + + buildType(Lint) + buildType(Checks) + + subProject { + id("Test") + name = "Test" + + subProject { + id("Jest") + name = "Jest" + + buildType(Jest) + buildType(XPackJest) + buildType(JestIntegration) + } + + buildType(ApiServerIntegration) + buildType(QuickTests) + buildType(AllTests) + } + + subProject { + id("OSS") + name = "OSS Distro" + + buildType(OssBuild) + + subProject { + id("OSS_Functional") + name = "Functional" + + buildType(OssCiGroups) + buildType(OssFirefox) + buildType(OssAccessibility) + buildType(OssPluginFunctional) + + subProject { + id("CIGroups") + name = "CI Groups" + + ossCiGroups.forEach { buildType(it) } + } + } + } + + subProject { + id("Default") + name = "Default Distro" + + buildType(DefaultBuild) + + subProject { + id("Default_Functional") + name = "Functional" + + buildType(DefaultCiGroups) + buildType(DefaultFirefox) + buildType(DefaultAccessibility) + buildType(DefaultSecuritySolution) + buildType(DefaultSavedObjectFieldMetrics) + + subProject { + id("Default_CIGroups") + name = "CI Groups" + + defaultCiGroups.forEach { buildType(it) } + } + } + } + + buildType(FullCi) + buildType(BaselineCi) + buildType(HourlyCi) + buildType(PullRequestCi) + } + + subProject(EsSnapshotsProject) + } +} diff --git a/.teamcity/src/templates/DefaultTemplate.kt b/.teamcity/src/templates/DefaultTemplate.kt new file mode 100644 index 000000000000..762218b72ab1 --- /dev/null +++ b/.teamcity/src/templates/DefaultTemplate.kt @@ -0,0 +1,25 @@ +package templates + +import jetbrains.buildServer.configs.kotlin.v2019_2.Template +import jetbrains.buildServer.configs.kotlin.v2019_2.buildFeatures.perfmon + +object DefaultTemplate : Template({ + name = "Default Template" + + requirements { + equals("system.cloud.profile_id", "kibana", "RQ_CLOUD_PROFILE_ID") + startsWith("teamcity.agent.name", "kibana-standard-2-", "RQ_AGENT_NAME") + } + + params { + param("env.HOME", "/var/lib/jenkins") // TODO once the agent images are sorted out + } + + features { + perfmon { } + } + + failureConditions { + executionTimeoutMin = 120 + } +}) diff --git a/.teamcity/src/templates/KibanaTemplate.kt b/.teamcity/src/templates/KibanaTemplate.kt new file mode 100644 index 000000000000..117c30ddb86e --- /dev/null +++ b/.teamcity/src/templates/KibanaTemplate.kt @@ -0,0 +1,141 @@ +package templates + +import vcs.Kibana +import jetbrains.buildServer.configs.kotlin.v2019_2.BuildStep +import jetbrains.buildServer.configs.kotlin.v2019_2.ParameterDisplay +import jetbrains.buildServer.configs.kotlin.v2019_2.Template +import jetbrains.buildServer.configs.kotlin.v2019_2.buildFeatures.PullRequests +import jetbrains.buildServer.configs.kotlin.v2019_2.buildFeatures.perfmon +import jetbrains.buildServer.configs.kotlin.v2019_2.buildFeatures.pullRequests +import jetbrains.buildServer.configs.kotlin.v2019_2.buildSteps.placeholder +import jetbrains.buildServer.configs.kotlin.v2019_2.buildSteps.script + +object KibanaTemplate : Template({ + name = "Kibana Template" + description = "For builds that need to check out kibana and execute against the repo using node" + + vcs { + root(Kibana) + + checkoutDir = "kibana" +// checkoutDir = "/dev/shm/%system.teamcity.buildType.id%/%system.build.number%/kibana" + } + + requirements { + equals("system.cloud.profile_id", "kibana", "RQ_CLOUD_PROFILE_ID") + startsWith("teamcity.agent.name", "kibana-standard-2-", "RQ_AGENT_NAME") + } + + features { + perfmon { } + pullRequests { + vcsRootExtId = "${Kibana.id}" + provider = github { + authType = token { + token = "credentialsJSON:07d22002-12de-4627-91c3-672bdb23b55b" + } + filterTargetBranch = "refs/heads/master_teamcity" + filterAuthorRole = PullRequests.GitHubRoleFilter.MEMBER + } + } + } + + failureConditions { + executionTimeoutMin = 120 + testFailure = false + } + + params { + param("env.CI", "true") + param("env.TEAMCITY_CI", "true") + param("env.HOME", "/var/lib/jenkins") // TODO once the agent images are sorted out + + // TODO remove these + param("env.GCS_UPLOAD_PREFIX", "INVALID_PREFIX") + param("env.CI_PARALLEL_PROCESS_NUMBER", "1") + + param("env.TEAMCITY_URL", "%teamcity.serverUrl%") + param("env.TEAMCITY_BUILD_URL", "%teamcity.serverUrl%/build/%teamcity.build.id%") + param("env.TEAMCITY_JOB_ID", "%system.teamcity.buildType.id%") + param("env.TEAMCITY_BUILD_ID", "%build.number%") + param("env.TEAMCITY_BUILD_NUMBER", "%teamcity.build.id%") + + param("env.GIT_BRANCH", "%vcsroot.branch%") + param("env.GIT_COMMIT", "%build.vcs.number%") + param("env.branch_specifier", "%vcsroot.branch%") + + password("env.KIBANA_CI_STATS_CONFIG", "", display = ParameterDisplay.HIDDEN) + password("env.CI_STATS_TOKEN", "credentialsJSON:ea975068-ca68-4da5-8189-ce90f4286bc0", display = ParameterDisplay.HIDDEN) + password("env.CI_STATS_HOST", "credentialsJSON:933ba93e-4b06-44c1-8724-8c536651f2b6", display = ParameterDisplay.HIDDEN) + + // TODO move these to vault once the configuration is finalized + // password("env.CI_STATS_TOKEN", "%vault:kibana-issues:secret/kibana-issues/dev/kibana_ci_stats!/api_token%", display = ParameterDisplay.HIDDEN) + // password("env.CI_STATS_HOST", "%vault:kibana-issues:secret/kibana-issues/dev/kibana_ci_stats!/api_host%", display = ParameterDisplay.HIDDEN) + + // TODO remove this once we are able to pull it out of vault and put it closer to the things that require it + password("env.GITHUB_TOKEN", "credentialsJSON:07d22002-12de-4627-91c3-672bdb23b55b", display = ParameterDisplay.HIDDEN) + password("env.KIBANA_CI_REPORTER_KEY", "", display = ParameterDisplay.HIDDEN) + password("env.KIBANA_CI_REPORTER_KEY_BASE64", "credentialsJSON:86878779-4cf7-4434-82af-5164a1b992fb", display = ParameterDisplay.HIDDEN) + + } + + steps { + script { + name = "Setup Environment" + scriptContent = + """ + #!/bin/bash + ./.ci/teamcity/setup_env.sh + """.trimIndent() + } + + script { + name = "Setup Node and Yarn" + scriptContent = + """ + #!/bin/bash + ./.ci/teamcity/setup_node.sh + """.trimIndent() + } + + script { + name = "Setup CI Stats" + scriptContent = + """ + #!/bin/bash + node .ci/teamcity/setup_ci_stats.js + """.trimIndent() + } + + script { + name = "Bootstrap" + scriptContent = + """ + #!/bin/bash + ./.ci/teamcity/bootstrap.sh + """.trimIndent() + } + + placeholder {} + + script { + name = "Set Build Status Success" + scriptContent = + """ + #!/bin/bash + echo "##teamcity[setParameter name='env.BUILD_STATUS' value='SUCCESS']" + """.trimIndent() + executionMode = BuildStep.ExecutionMode.RUN_ON_SUCCESS + } + + script { + name = "CI Stats Complete" + scriptContent = + """ + #!/bin/bash + node .ci/teamcity/ci_stats_complete.js + """.trimIndent() + executionMode = BuildStep.ExecutionMode.RUN_ON_FAILURE + } + } +}) diff --git a/.teamcity/src/vcs/Elasticsearch.kt b/.teamcity/src/vcs/Elasticsearch.kt new file mode 100644 index 000000000000..ab7120b85444 --- /dev/null +++ b/.teamcity/src/vcs/Elasticsearch.kt @@ -0,0 +1,11 @@ +package vcs + +import jetbrains.buildServer.configs.kotlin.v2019_2.vcs.GitVcsRoot + +object Elasticsearch : GitVcsRoot({ + id("elasticsearch_master") + + name = "elasticsearch / master" + url = "https://github.com/elastic/elasticsearch.git" + branch = "refs/heads/master" +}) diff --git a/.teamcity/src/vcs/Kibana.kt b/.teamcity/src/vcs/Kibana.kt new file mode 100644 index 000000000000..d847a1565e6e --- /dev/null +++ b/.teamcity/src/vcs/Kibana.kt @@ -0,0 +1,11 @@ +package vcs + +import jetbrains.buildServer.configs.kotlin.v2019_2.vcs.GitVcsRoot + +object Kibana : GitVcsRoot({ + id("kibana_master") + + name = "kibana / master" + url = "https://github.com/elastic/kibana.git" + branch = "refs/heads/master_teamcity" +}) diff --git a/.teamcity/tests/projects/KibanaTest.kt b/.teamcity/tests/projects/KibanaTest.kt new file mode 100644 index 000000000000..677effec5be6 --- /dev/null +++ b/.teamcity/tests/projects/KibanaTest.kt @@ -0,0 +1,27 @@ +package projects + +import org.junit.Assert.* +import org.junit.Test + +val TestConfig = KibanaConfiguration { + agentNetwork = "network" + agentSubnet = "subnet" +} + +class KibanaTest { + @Test + fun test_Default_Configuration_Exists() { + assertNotNull(kibanaConfiguration) + Kibana() + assertEquals("teamcity", kibanaConfiguration.agentNetwork) + } + + @Test + fun test_CloudImages_Exist() { + val project = Kibana(TestConfig) + + assertTrue(project.features.items.any { + it.type == "CloudImage" && it.params.any { param -> param.name == "network" && param.value == "network"} + }) + } +} diff --git a/packages/kbn-test/src/failed_tests_reporter/report_failure.test.ts b/packages/kbn-test/src/failed_tests_reporter/report_failure.test.ts index 5bbc72fe04e8..910c9ad24670 100644 --- a/packages/kbn-test/src/failed_tests_reporter/report_failure.test.ts +++ b/packages/kbn-test/src/failed_tests_reporter/report_failure.test.ts @@ -19,7 +19,7 @@ import dedent from 'dedent'; -import { createFailureIssue, updateFailureIssue } from './report_failure'; +import { createFailureIssue, getCiType, updateFailureIssue } from './report_failure'; jest.mock('./github_api'); const { GithubApi } = jest.requireMock('./github_api'); @@ -51,7 +51,7 @@ describe('createFailureIssue()', () => { this is the failure text \`\`\` - First failure: [Jenkins Build](https://build-url) + First failure: [${getCiType()} Build](https://build-url) ", Array [ @@ -111,7 +111,7 @@ describe('updateFailureIssue()', () => { "calls": Array [ Array [ 1234, - "New failure: [Jenkins Build](https://build-url)", + "New failure: [${getCiType()} Build](https://build-url)", ], ], "results": Array [ diff --git a/packages/kbn-test/src/failed_tests_reporter/report_failure.ts b/packages/kbn-test/src/failed_tests_reporter/report_failure.ts index 1413d0549845..30ec6ab93956 100644 --- a/packages/kbn-test/src/failed_tests_reporter/report_failure.ts +++ b/packages/kbn-test/src/failed_tests_reporter/report_failure.ts @@ -21,6 +21,10 @@ import { TestFailure } from './get_failures'; import { GithubIssueMini, GithubApi } from './github_api'; import { getIssueMetadata, updateIssueMetadata } from './issue_metadata'; +export function getCiType() { + return process.env.TEAMCITY_CI ? 'TeamCity' : 'Jenkins'; +} + export async function createFailureIssue(buildUrl: string, failure: TestFailure, api: GithubApi) { const title = `Failing test: ${failure.classname} - ${failure.name}`; @@ -32,7 +36,7 @@ export async function createFailureIssue(buildUrl: string, failure: TestFailure, failure.failure, '```', '', - `First failure: [Jenkins Build](${buildUrl})`, + `First failure: [${getCiType()} Build](${buildUrl})`, ].join('\n'), { 'test.class': failure.classname, @@ -52,7 +56,7 @@ export async function updateFailureIssue(buildUrl: string, issue: GithubIssueMin }); await api.editIssueBodyAndEnsureOpen(issue.number, newBody); - await api.addIssueComment(issue.number, `New failure: [Jenkins Build](${buildUrl})`); + await api.addIssueComment(issue.number, `New failure: [${getCiType()} Build](${buildUrl})`); return newCount; } diff --git a/packages/kbn-test/src/failed_tests_reporter/run_failed_tests_reporter_cli.ts b/packages/kbn-test/src/failed_tests_reporter/run_failed_tests_reporter_cli.ts index 93616ce78a04..9010e324bb39 100644 --- a/packages/kbn-test/src/failed_tests_reporter/run_failed_tests_reporter_cli.ts +++ b/packages/kbn-test/src/failed_tests_reporter/run_failed_tests_reporter_cli.ts @@ -33,6 +33,17 @@ import { getReportMessageIter } from './report_metadata'; const DEFAULT_PATTERNS = [Path.resolve(REPO_ROOT, 'target/junit/**/*.xml')]; +const getBranch = () => { + if (process.env.TEAMCITY_CI) { + return (process.env.GIT_BRANCH || '').replace(/^refs\/heads\//, ''); + } else { + // JOB_NAME is formatted as `elastic+kibana+7.x` in some places and `elastic+kibana+7.x/JOB=kibana-intake,node=immutable` in others + const jobNameSplit = (process.env.JOB_NAME || '').split(/\+|\//); + const branch = jobNameSplit.length >= 3 ? jobNameSplit[2] : process.env.GIT_BRANCH; + return branch; + } +}; + export function runFailedTestsReporterCli() { run( async ({ log, flags }) => { @@ -44,16 +55,15 @@ export function runFailedTestsReporterCli() { } if (updateGithub) { - // JOB_NAME is formatted as `elastic+kibana+7.x` in some places and `elastic+kibana+7.x/JOB=kibana-intake,node=immutable` in others - const jobNameSplit = (process.env.JOB_NAME || '').split(/\+|\//); - const branch = jobNameSplit.length >= 3 ? jobNameSplit[2] : process.env.GIT_BRANCH; + const branch = getBranch(); if (!branch) { throw createFailError( 'Unable to determine originating branch from job name or other environment variables' ); } - const isPr = !!process.env.ghprbPullId; + // ghprbPullId check can be removed once there are no PR jobs running on Jenkins + const isPr = !!process.env.GITHUB_PR_NUMBER || !!process.env.ghprbPullId; const isMasterOrVersion = branch === 'master' || branch.match(/^\d+\.(x|\d+)$/); if (!isMasterOrVersion || isPr) { log.info('Failure issues only created on master/version branch jobs'); @@ -69,7 +79,9 @@ export function runFailedTestsReporterCli() { const buildUrl = flags['build-url'] || (updateGithub ? '' : 'http://buildUrl'); if (typeof buildUrl !== 'string' || !buildUrl) { - throw createFlagError('Missing --build-url or process.env.BUILD_URL'); + throw createFlagError( + 'Missing --build-url, process.env.TEAMCITY_BUILD_URL, or process.env.BUILD_URL' + ); } const patterns = flags._.length ? flags._ : DEFAULT_PATTERNS; @@ -161,12 +173,12 @@ export function runFailedTestsReporterCli() { default: { 'github-update': true, 'report-update': true, - 'build-url': process.env.BUILD_URL, + 'build-url': process.env.TEAMCITY_BUILD_URL || process.env.BUILD_URL, }, help: ` --no-github-update Execute the CLI without writing to Github --no-report-update Execute the CLI without writing to the JUnit reports - --build-url URL of the failed build, defaults to process.env.BUILD_URL + --build-url URL of the failed build, defaults to process.env.TEAMCITY_BUILD_URL or process.env.BUILD_URL `, }, } diff --git a/packages/kbn-test/src/mocha/__tests__/junit_report_generation.js b/packages/kbn-test/src/mocha/__tests__/junit_report_generation.js index 407ab37123d5..605ad38efbc9 100644 --- a/packages/kbn-test/src/mocha/__tests__/junit_report_generation.js +++ b/packages/kbn-test/src/mocha/__tests__/junit_report_generation.js @@ -67,6 +67,7 @@ describe('dev/mocha/junit report generation', () => { expect(testsuite).to.eql({ $: { failures: '2', + name: 'test', skipped: '1', tests: '4', time: testsuite.$.time, diff --git a/packages/kbn-test/src/mocha/junit_report_generation.js b/packages/kbn-test/src/mocha/junit_report_generation.js index 84d488bd8b5a..de28fceb967e 100644 --- a/packages/kbn-test/src/mocha/junit_report_generation.js +++ b/packages/kbn-test/src/mocha/junit_report_generation.js @@ -108,6 +108,7 @@ export function setupJUnitReportGeneration(runner, options = {}) { ); const testsuitesEl = builder.ele('testsuite', { + name: reportName, timestamp: new Date(stats.startTime).toISOString().slice(0, -5), time: getDuration(stats), tests: allTests.length + failedHooks.length, diff --git a/src/dev/precommit_hook/casing_check_config.js b/src/dev/precommit_hook/casing_check_config.js index d859c7e45fa2..8448d20aa2fc 100644 --- a/src/dev/precommit_hook/casing_check_config.js +++ b/src/dev/precommit_hook/casing_check_config.js @@ -70,8 +70,11 @@ export const IGNORE_FILE_GLOBS = [ 'x-pack/plugins/apm/e2e/**/*', 'x-pack/plugins/maps/server/fonts/**/*', + // packages for the ingest manager's api integration tests could be valid semver which has dashes 'x-pack/test/fleet_api_integration/apis/fixtures/test_packages/**/*', + + '.teamcity/**/*', ]; /** diff --git a/x-pack/test/api_integration/apis/security_solution/feature_controls.ts b/x-pack/test/api_integration/apis/security_solution/feature_controls.ts index c2dfd28d5c84..0137a90ce981 100644 --- a/x-pack/test/api_integration/apis/security_solution/feature_controls.ts +++ b/x-pack/test/api_integration/apis/security_solution/feature_controls.ts @@ -82,10 +82,11 @@ export default function ({ getService }: FtrProviderContext) { }; describe('feature controls', () => { - let isProd = false; + let isProdOrCi = false; before(() => { const kbnConfig = config.get('servers.kibana'); - isProd = kbnConfig.hostname === 'localhost' && kbnConfig.port === 5620 ? false : true; + isProdOrCi = + !!process.env.CI || !(kbnConfig.hostname === 'localhost' && kbnConfig.port === 5620); }); it(`APIs can't be accessed by user with no privileges`, async () => { const username = 'logstash_read'; @@ -135,7 +136,7 @@ export default function ({ getService }: FtrProviderContext) { expectGraphQLResponse(graphQLResult); const graphQLIResult = await executeGraphIQLRequest(username, password); - if (!isProd) { + if (!isProdOrCi) { expectGraphIQLResponse(graphQLIResult); } else { expectGraphIQL404(graphQLIResult); @@ -234,7 +235,7 @@ export default function ({ getService }: FtrProviderContext) { expectGraphQLResponse(graphQLResult); const graphQLIResult = await executeGraphIQLRequest(username, password, space1Id); - if (!isProd) { + if (!isProdOrCi) { expectGraphIQLResponse(graphQLIResult); } else { expectGraphIQL404(graphQLIResult);