diff --git a/.ci/Jenkinsfile b/.ci/Jenkinsfile index 2770b21a4b..6637e9d0d2 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: '7.x-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 8046f808f0..7182e8a0a0 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:-7.x-SNAPSHOT}" + image: "docker.elastic.co/${kibanaDockerNamespace:-beats}/kibana:${kibanaVersion:-7.x-SNAPSHOT}" ports: - "5601:5601" volumes: diff --git a/e2e/README.md b/e2e/README.md index b34d25bc2a..5b83f02b43 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 b6d64b5908..fc93f230b5 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=7.x-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/7.x-SNAPSHOT/elastic-agent export ELASTIC_AGENT_DOWNLOAD_URL="https://snapshots.elastic.co/7.12.0-069dfaa4/downloads/beats/elastic-agent/elastic-agent-7.12.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 de5bcbb1c2..c8c90eb812 100644 --- a/e2e/_suites/fleet/ingest_manager_test.go +++ b/e2e/_suites/fleet/ingest_manager_test.go @@ -53,6 +53,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 @@ -98,8 +105,14 @@ func InitializeIngestManagerTestSuite(ctx *godog.TestSuiteContext) { workDir, _ := os.Getwd() profileEnv = map[string]string{ - "stackVersion": stackVersion, "kibanaConfigPath": path.Join(workDir, "configurations", "kibana.config.yml"), + "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 4f6ea68f89..7eda9defdf 100644 --- a/e2e/_suites/fleet/stand-alone.go +++ b/e2e/_suites/fleet/stand-alone.go @@ -114,7 +114,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 a11d399795..375ac61662 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 473aa863b4..e0ee71f2c2 100644 --- a/e2e/_suites/metricbeat/README.md +++ b/e2e/_suites/metricbeat/README.md @@ -44,9 +44,9 @@ 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="7.x-SNAPSHOT" - export BEAT_VERSIONBEAT_VERSION="7.x-SNAPSHOT" + export BEAT_VERSION="7.x-SNAPSHOT" # or # This environment variable will use the snapshots produced by Beats CI export BEATS_USE_CI_SNAPSHOTS="true" diff --git a/e2e/_suites/metricbeat/metricbeat_test.go b/e2e/_suites/metricbeat/metricbeat_test.go index 90b2ae59bf..3252cd4b65 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