diff --git a/.ci/Jenkinsfile b/.ci/Jenkinsfile index ec80a6dfce..26f1aaf778 100644 --- a/.ci/Jenkinsfile +++ b/.ci/Jenkinsfile @@ -46,6 +46,7 @@ pipeline { booleanParam(name: "BEATS_USE_CI_SNAPSHOTS", defaultValue: false, description: "If it's needed to use the binary snapshots produced by Beats CI instead of the official releases") choice(name: 'LOG_LEVEL', choices: ['DEBUG', 'TRACE', 'INFO'], description: 'Log level to be used') choice(name: 'TIMEOUT_FACTOR', choices: ['5', '3', '7', '11'], description: 'Max number of minutes for timeout backoff strategies') + string(name: 'KIBANA_VERSION', defaultValue: '', description: 'Docker tag of the kibana to be used for the tests. It will refer to an image related to a Kibana PR, under the Observability-CI namespace') string(name: 'STACK_VERSION', defaultValue: '8.0.0-SNAPSHOT', description: 'SemVer version of the stack to be used for the tests.') string(name: 'HELM_CHART_VERSION', defaultValue: '7.11.2', description: 'SemVer version of Helm chart to be used.') string(name: 'HELM_VERSION', defaultValue: '3.5.2', description: 'SemVer version of Helm to be used.') @@ -53,7 +54,7 @@ pipeline { string(name: 'HELM_KUBERNETES_VERSION', defaultValue: '1.18.2', description: 'SemVer version of Kubernetes to be used.') string(name: 'GITHUB_CHECK_NAME', defaultValue: '', description: 'Name of the GitHub check to be updated. Only if this build is triggered from another parent stream.') string(name: 'GITHUB_CHECK_REPO', defaultValue: '', description: 'Name of the GitHub repo to be updated. Only if this build is triggered from another parent stream.') - string(name: 'GITHUB_CHECK_SHA1', defaultValue: '', description: 'Name of the GitHub repo to be updated. Only if this build is triggered from another parent stream.') + string(name: 'GITHUB_CHECK_SHA1', defaultValue: '', description: 'Git SHA for the Beats upstream project (branch or PR)') } stages { stage('Initializing'){ @@ -69,6 +70,7 @@ pipeline { ELASTIC_AGENT_DOWNLOAD_URL = "${params.ELASTIC_AGENT_DOWNLOAD_URL.trim()}" BEAT_VERSION = "${params.BEAT_VERSION.trim()}" BEATS_USE_CI_SNAPSHOTS = "${params.BEATS_USE_CI_SNAPSHOTS}" + KIBANA_VERSION = "${params.KIBANA_VERSION.trim()}" STACK_VERSION = "${params.STACK_VERSION.trim()}" FORCE_SKIP_GIT_CHECKS = "${params.forceSkipGitChecks}" FORCE_SKIP_PRESUBMIT = "${params.forceSkipPresubmit}" diff --git a/.ci/e2eKibana.groovy b/.ci/e2eKibana.groovy new file mode 100644 index 0000000000..16c449b494 --- /dev/null +++ b/.ci/e2eKibana.groovy @@ -0,0 +1,129 @@ +#!/usr/bin/env groovy + +@Library('apm@current') _ + +pipeline { + agent none + environment { + REPO = 'kibana' + BASE_DIR = "src/github.com/elastic/${env.REPO}" + GITHUB_CHECK_E2E_TESTS_NAME = 'E2E Tests' + PIPELINE_LOG_LEVEL = "INFO" + } + options { + timeout(time: 3, unit: 'HOURS') + buildDiscarder(logRotator(numToKeepStr: '20', artifactNumToKeepStr: '20', daysToKeepStr: '30')) + timestamps() + ansiColor('xterm') + disableResume() + durabilityHint('PERFORMANCE_OPTIMIZED') + disableConcurrentBuilds() + } + // http://JENKINS_URL/generic-webhook-trigger/invoke + // Pull requests events: https://docs.github.com/en/developers/webhooks-and-events/github-event-types#pullrequestevent + triggers { + GenericTrigger( + genericVariables: [ + [key: 'GT_REPO', value: '$.repository.full_name'], + [key: 'GT_BASE_REF', value: '$.pull_request.base.ref'], + [key: 'GT_PR', value: '$.issue.number'], + [key: 'GT_PR_HEAD_SHA', value: '$.pull_request.head.sha'], + [key: 'GT_BODY', value: '$.comment.body'], + [key: 'GT_COMMENT_ID', value: '$.comment.id'] + ], + genericHeaderVariables: [ + [key: 'x-github-event', regexpFilter: 'comment'] + ], + causeString: 'Triggered on comment: $GT_BODY', + printContributedVariables: false, + printPostContent: false, + silentResponse: true, + regexpFilterText: '$GT_REPO$GT_BODY', + regexpFilterExpression: '^elastic/kibana/run-fleet-e2e-tests$' + ) + } + parameters { + string(name: 'kibana_pr', defaultValue: "master", description: "PR ID to use to build the Docker image. (e.g 10000)") + } + stages { + stage('Process GitHub Event') { + steps { + checkPermissions() + buildKibanaDockerImage(refspec: getBranch()) + catchError(buildResult: 'UNSTABLE', message: 'Unable to run e2e tests', stageResult: 'FAILURE') { + runE2ETests('fleet') + } + } + } + } +} + +def checkPermissions(){ + if(env.GT_PR){ + if(!githubPrCheckApproved(changeId: "${env.GT_PR}", org: 'elastic', repo: 'kibana')){ + error("Only PRs from Elasticians can be tested with Fleet E2E tests") + } + + if(!hasCommentAuthorWritePermissions(env.GT_PR, env.GT_COMMENT_ID)){ + error("Only Elasticians can trigger Fleet E2E tests") + } + } +} + +def getBranch(){ + if(env.GT_PR){ + return "PR/${env.GT_PR}" + } + + return "PR/${params.kibana_pr}" +} + +def getDockerTag(){ + if(env.GT_PR){ + return "${env.GT_PR_HEAD_SHA}" + } + + // we are going to use the 'pr12345' tag + return "pr${params.kibana_pr}" +} + +def hasCommentAuthorWritePermissions(prId, commentId){ + def repoName = "elastic/kibana" + def token = getGithubToken() + def url = "https://api.github.com/repos/${repoName}/issues/${prId}/comments/${commentId}" + def comment = githubApiCall(token: token, url: url, noCache: true) + def json = githubRepoGetUserPermission(token: token, repo: repoName, user: comment?.user?.login) + + return json?.permission == 'admin' || json?.permission == 'write' +} + +def runE2ETests(String suite) { + log(level: 'DEBUG', text: "Triggering '${suite}' E2E tests for PR-${env.GT_PR}.") + + // Kibana's maintenance branches follow the 7.11, 7.12 schema. + def branchName = "${env.GT_BASE_REF}.x" + def e2eTestsPipeline = "e2e-tests/e2e-testing-mbp/${branchName}" + + def parameters = [ + booleanParam(name: 'forceSkipGitChecks', value: true), + booleanParam(name: 'forceSkipPresubmit', value: true), + booleanParam(name: 'notifyOnGreenBuilds', value: false), + booleanParam(name: 'BEATS_USE_CI_SNAPSHOTS', value: true), + string(name: 'runTestsSuites', value: suite), + string(name: 'GITHUB_CHECK_NAME', value: env.GITHUB_CHECK_E2E_TESTS_NAME), + string(name: 'GITHUB_CHECK_REPO', value: env.REPO), + string(name: 'KIBANA_VERSION', value: getDockerTag()), + ] + + build(job: "${e2eTestsPipeline}", + parameters: parameters, + propagate: false, + wait: false + ) + +/* + // commented out to avoid sending Github statuses to Kibana PRs + def notifyContext = "${env.pr_head_sha}" + githubNotify(context: "${notifyContext}", description: "${notifyContext} ...", status: 'PENDING', targetUrl: "${env.JENKINS_URL}search/?q=${e2eTestsPipeline.replaceAll('/','+')}") +*/ +} diff --git a/.ci/jobs/kibana-e2e-tests.yml b/.ci/jobs/kibana-e2e-tests.yml new file mode 100644 index 0000000000..434e66cc82 --- /dev/null +++ b/.ci/jobs/kibana-e2e-tests.yml @@ -0,0 +1,20 @@ +--- +- job: + name: e2e-tests/e2e-testing-kibana-fleet + display-name: Fleet UI e2e tests Pipeline + description: Jenkins pipeline to run the end2end tests for the Fleet UI + project-type: pipeline + disabled: false + pipeline-scm: + script-path: .ci/e2eKibana.groovy + scm: + - git: + url: git@github.com:elastic/e2e-testing.git + refspec: +refs/heads/*:refs/remotes/origin/* +refs/pull/*/head:refs/remotes/origin/pr/* + wipe-workspace: 'True' + name: origin + shallow-clone: true + credentials-id: f6c7695a-671e-4f4f-a331-acdce44ff9ba + reference-repo: /var/lib/jenkins/.git-references/e2e-testing.git + branches: + - $branch_specifier diff --git a/cli/config/compose/profiles/fleet/docker-compose.yml b/cli/config/compose/profiles/fleet/docker-compose.yml index 5d22bf2088..2a740c3335 100644 --- a/cli/config/compose/profiles/fleet/docker-compose.yml +++ b/cli/config/compose/profiles/fleet/docker-compose.yml @@ -29,7 +29,7 @@ services: test: "curl -f http://localhost:5601/login | grep kbn-injected-metadata 2>&1 >/dev/null" retries: 600 interval: 1s - image: "docker.elastic.co/kibana/kibana:${stackVersion:-8.0.0-SNAPSHOT}" + image: "docker.elastic.co/${kibanaDockerNamespace:-beats}/kibana:${kibanaVersion:-8.0.0-SNAPSHOT}" ports: - "5601:5601" volumes: diff --git a/e2e/README.md b/e2e/README.md index af1c88d35d..f833d64aa3 100644 --- a/e2e/README.md +++ b/e2e/README.md @@ -147,7 +147,8 @@ The following environment variables affect how the tests are run in both the CI - `BEATS_USE_CI_SNAPSHOTS`: Set this environment variable to `true` if it's needed to use the binary snapshots produced by Beats CI instead of the official releases. The snapshots will be downloaded from a bucket in Google Cloud Storage. This variable is used by the Beats repository, when testing the artifacts generated by the packaging job. Default: `false`. - `LOG_LEVEL`: Set this environment variable to `TRACE`, `DEBUG`, `INFO`, `WARN`, `ERROR` or `FATAL` to set the log level in the project. Default: `INFO`. - `DEVELOPER_MODE`: Set this environment variable to `true` to activate developer mode, which means not destroying the services provisioned by the test framework. Default: `false`. -- `STACK_VERSION`. Set this environment variable to the proper version of the Elastic Stack (Elasticsearch and Kibana) to be used in the current execution. The default value depens on the branch you are targeting your work. +- `KIBANA_VERSION`. Set this environment variable to the proper version of the Kibana instance to be used in the current execution, which should be used for the Docker tag of the kibana instance. It will refer to an image related to a Kibana PR, under the Observability-CI namespace. Default is empty +- `STACK_VERSION`. Set this environment variable to the proper version of the Elasticsearch to be used in the current execution. The default value depens on the branch you are targeting your work. - **master (Fleet):** https://github.com/elastic/e2e-testing/blob/0446248bae1ff604219735998841a21a7576bfdd/e2e/_suites/fleet/ingest-manager_test.go#L39 - **master (Integrations):** https://github.com/elastic/e2e-testing/blob/0446248bae1ff604219735998841a21a7576bfdd/e2e/_suites/metricbeat/metricbeat_test.go#L30 - `TIMEOUT_FACTOR`: Set this environment variable to an integer number, which represents the factor to be used while waiting for resources within the tests. I.e. waiting for Kibana needs around 30 seconds. Instead of hardcoding 30 seconds, or 3 minutes, in the code, we use a backoff strategy to wait until an amount of time, specific per situation, multiplying it by the timeout factor. With that in mind, we are able to set a higher factor on CI without changing the code, and the developer is able to locally set specific conditions when running the tests on slower machines. Default: `3`. diff --git a/e2e/_suites/fleet/README.md b/e2e/_suites/fleet/README.md index 030fa2f497..94cf727cdd 100644 --- a/e2e/_suites/fleet/README.md +++ b/e2e/_suites/fleet/README.md @@ -44,8 +44,10 @@ To change it, please use Docker UI, go to `Preferences > Resources > File Sharin This is an example of the optional configuration: ```shell - # There should be a Docker image for the runtime dependencies (elasticsearch, kibana, package registry) + # There should be a Docker image for the runtime dependencies (elasticsearch, package registry) export STACK_VERSION=8.0.0-SNAPSHOT + # There should be a Docker image for the runtime dependencies (kibana) + export KIBANA_VERSION=pr12345 # (Fleet mode) This environment variable will use a fixed version of the Elastic agent binary, obtained from # https://artifacts-api.elastic.co/v1/search/8.0.0-SNAPSHOT/elastic-agent export ELASTIC_AGENT_DOWNLOAD_URL="https://snapshots.elastic.co/8.0.0-59098054/downloads/beats/elastic-agent/elastic-agent-8.0.0-SNAPSHOT-linux-x86_64.tar.gz" diff --git a/e2e/_suites/fleet/ingest_manager_test.go b/e2e/_suites/fleet/ingest_manager_test.go index ea8a411b93..69b3844d12 100644 --- a/e2e/_suites/fleet/ingest_manager_test.go +++ b/e2e/_suites/fleet/ingest_manager_test.go @@ -52,6 +52,13 @@ func setUpSuite() { stackVersion = shell.GetEnv("STACK_VERSION", stackVersion) stackVersion = e2e.GetElasticArtifactVersion(stackVersion) + kibanaVersion = shell.GetEnv("KIBANA_VERSION", "") + if kibanaVersion == "" { + // we want to deploy a released version for Kibana + // if not set, let's use stackVersion + kibanaVersion = e2e.GetElasticArtifactVersion(stackVersion) + } + imts = IngestManagerTestSuite{ Fleet: &FleetTestSuite{ Installers: map[string]ElasticAgentInstaller{}, // do not pre-initialise the map @@ -96,7 +103,13 @@ func InitializeIngestManagerTestSuite(ctx *godog.TestSuiteContext) { log.Trace("Installing Fleet runtime dependencies") profileEnv = map[string]string{ - "stackVersion": stackVersion, + "kibanaVersion": kibanaVersion, + "stackVersion": stackVersion, + } + + profileEnv["kibanaDockerNamespace"] = "observability-ci" + if kibanaVersion == "" { + profileEnv["kibanaDockerNamespace"] = "kibana" } profile := FleetProfileName diff --git a/e2e/_suites/fleet/stand-alone.go b/e2e/_suites/fleet/stand-alone.go index f516bdd586..ba1c658bd3 100644 --- a/e2e/_suites/fleet/stand-alone.go +++ b/e2e/_suites/fleet/stand-alone.go @@ -92,7 +92,7 @@ func (sats *StandAloneTestSuite) startAgent(image string, env map[string]string) profileEnv["elasticAgentDockerImageSuffix"] = "-" + image } - profileEnv["elasticAgentDockerNamespace"] = e2e.GetDockerNamespaceEnvVar() + profileEnv["elasticAgentDockerNamespace"] = e2e.GetDockerNamespaceEnvVar("beats") containerName := fmt.Sprintf("%s_%s_%d", FleetProfileName, ElasticAgentServiceName, 1) diff --git a/e2e/_suites/fleet/world.go b/e2e/_suites/fleet/world.go index 4d28ccfcdf..c62233f296 100644 --- a/e2e/_suites/fleet/world.go +++ b/e2e/_suites/fleet/world.go @@ -36,6 +36,10 @@ var agentVersion = agentVersionBase // It can be overriden by ELASTIC_AGENT_STALE_VERSION env var. Using latest GA as a default. var agentStaleVersion = "7.11-SNAPSHOT" +// kibanaVersion is the version of the kibana to use +// It can be overriden by KIBANA_VERSION env var +var kibanaVersion = agentVersionBase + // stackVersion is the version of the stack to use // It can be overriden by STACK_VERSION env var var stackVersion = agentVersionBase diff --git a/e2e/_suites/metricbeat/README.md b/e2e/_suites/metricbeat/README.md index f9f2be6388..0c36e002b2 100644 --- a/e2e/_suites/metricbeat/README.md +++ b/e2e/_suites/metricbeat/README.md @@ -44,7 +44,7 @@ To change it, please use Docker UI, go to `Preferences > Resources > File Sharin This is an example of the optional configuration: ```shell - # There should be a Docker image for the runtime dependencies (elasticsearch, kibana, package registry) + # There should be a Docker image for the runtime dependencies (elasticsearch) export STACK_VERSION="8.0.0-SNAPSHOT" export BEAT_VERSION="8.0.0-SNAPSHOT" # or diff --git a/e2e/_suites/metricbeat/metricbeat_test.go b/e2e/_suites/metricbeat/metricbeat_test.go index 6d3b4ed831..e8a3c03631 100644 --- a/e2e/_suites/metricbeat/metricbeat_test.go +++ b/e2e/_suites/metricbeat/metricbeat_test.go @@ -446,7 +446,7 @@ func (mts *MetricbeatTestSuite) runMetricbeatService() error { "serviceName": mts.ServiceName, } - env["metricbeatDockerNamespace"] = e2e.GetDockerNamespaceEnvVar() + env["metricbeatDockerNamespace"] = e2e.GetDockerNamespaceEnvVar("beats") env["metricbeatPlatform"] = "linux/amd64" err := serviceManager.AddServicesToCompose(testSuite.currentContext, "metricbeat", []string{"metricbeat"}, env) diff --git a/e2e/utils.go b/e2e/utils.go index 3cc414e456..6ccfaa5c27 100644 --- a/e2e/utils.go +++ b/e2e/utils.go @@ -578,14 +578,14 @@ func Sleep(duration time.Duration) error { // GetDockerNamespaceEnvVar returns the Docker namespace whether we use one of the CI snapshots or // the images produced by local Beats build, or not. -// If an error occurred reading the environment, wil return 'beats' as fallback -func GetDockerNamespaceEnvVar() string { +// If an error occurred reading the environment, will return the passed namespace as fallback +func GetDockerNamespaceEnvVar(fallback string) string { beatsLocalPath := shell.GetEnv("BEATS_LOCAL_PATH", "") useCISnapshots := shell.GetEnvBool("BEATS_USE_CI_SNAPSHOTS") if useCISnapshots || beatsLocalPath != "" { return "observability-ci" } - return "beats" + return fallback } // WaitForProcess polls a container executing "ps" command until the process is in the desired state (present or not), diff --git a/e2e/utils_test.go b/e2e/utils_test.go index 7e04790511..0d1d154c43 100644 --- a/e2e/utils_test.go +++ b/e2e/utils_test.go @@ -225,6 +225,21 @@ func TestGetBucketSearchNextPageParam_HasNoMorePages(t *testing.T) { assert.True(t, param == "") } +func TestGetDockerNamespaceEnvVar(t *testing.T) { + t.Run("Returns fallback when environment variable is not set", func(t *testing.T) { + namespace := GetDockerNamespaceEnvVar("beats") + assert.True(t, namespace == "beats") + }) + + t.Run("Returns Observability CI when environment variable is set", func(t *testing.T) { + defer os.Unsetenv("BEATS_USE_CI_SNAPSHOTS") + os.Setenv("BEATS_USE_CI_SNAPSHOTS", "true") + + namespace := GetDockerNamespaceEnvVar("beats") + assert.True(t, namespace == "observability-ci") + }) +} + func TestGetGCPBucketCoordinates_Commits(t *testing.T) { artifact := "elastic-agent" version := testVersion