diff --git a/.ci/Makefile b/.ci/Makefile index 588ede8ea..acec7e96d 100644 --- a/.ci/Makefile +++ b/.ci/Makefile @@ -1,11 +1,21 @@ ifeq ($(DOCKERFILE),) ifeq ($(BUILD_ARCHITECTURE), linuxmusl-x86-64) DOCKERFILE := Dockerfile.alpine + else ifeq ($(BUILD_ARCHITECTURE), linuxmusl-arm64) + DOCKERFILE := Dockerfile.alpine else DOCKERFILE := Dockerfile endif endif +DOCKER_PLATFORM := linux/x86_64 +ifeq ($(BUILD_ARCHITECTURE), linux-arm64) + DOCKER_PLATFORM := linux/arm64 +else ifeq ($(BUILD_ARCHITECTURE), linuxmusl-arm64) + DOCKER_PLATFORM := linux/arm64 +endif + + SHELL=/bin/bash -o pipefail MAKEFLAGS += --no-print-directory IMAGE := test-php @@ -45,6 +55,7 @@ prepare: ## Build docker image for building and testing the project docker build \ --build-arg PHP_VERSION=${PHP_VERSION} --build-arg SEL_DISTRO=${SEL_DISTRO}\ --tag $(IMAGE):${PHP_VERSION}$(SUFFIX) \ + --platform $(DOCKER_PLATFORM) \ -f ${DOCKERFILE} . @echo "::endgroup::" @@ -59,8 +70,8 @@ build: -w /source/agent/native \ -e CONAN_USER_HOME=$(CONAN_USER_HOME) \ elasticobservability/apm-agent-php-dev:native-build-gcc-12.2.0-$(BUILD_ARCHITECTURE)-0.0.2 \ - sh -c "cmake --preset $(BUILD_ARCHITECTURE)-release \ - && cmake --build --preset $(BUILD_ARCHITECTURE)-release -j$(nproc)\ + sh -c "cmake --preset $(BUILD_ARCHITECTURE)-release \ + && cmake --build --preset $(BUILD_ARCHITECTURE)-release \ && ctest --preset $(BUILD_ARCHITECTURE)-release --verbose" @echo "::endgroup::" diff --git a/.ci/env_vars_for_external_services_for_component_tests_on_host.sh b/.ci/env_vars_for_external_services_for_component_tests_on_host.sh index 2f9218607..39b38de8a 100644 --- a/.ci/env_vars_for_external_services_for_component_tests_on_host.sh +++ b/.ci/env_vars_for_external_services_for_component_tests_on_host.sh @@ -11,4 +11,4 @@ export ELASTIC_APM_PHP_TESTS_MYSQL_PORT=43306 dockerComposeCmdFilesPart="-f ${thisScriptDir}/docker-compose_external_services_for_component_tests.yml" dockerComposeCmdFilesPart="${dockerComposeCmdFilesPart} -f ${thisScriptDir}/docker-compose_external_services_for_component_tests_on_host.yml" -export ELASTIC_APM_PHP_TESTS_EXTERNAL_SERVICES_START_CMD="docker-compose ${dockerComposeCmdFilesPart} up -d" +export ELASTIC_APM_PHP_TESTS_EXTERNAL_SERVICES_START_CMD="docker compose ${dockerComposeCmdFilesPart} up -d" diff --git a/.ci/generate_package_lifecycle_test_matrix.sh b/.ci/generate_package_lifecycle_test_matrix.sh index d18852b66..6ed00d39a 100755 --- a/.ci/generate_package_lifecycle_test_matrix.sh +++ b/.ci/generate_package_lifecycle_test_matrix.sh @@ -106,8 +106,13 @@ function generateAgentUpgradeRows () { local testingType=agent-upgrade local appHostKindShortName=all local testsGroup=smoke + for phpVersion in 7.4 ; do + for linuxPackageType in rpm ; do + echo "${phpVersion},${linuxPackageType},${testingType},${appHostKindShortName},${testsGroup}" + done + done for phpVersion in 7.4 "$(latestSupportedPhpVersion)" ; do - for linuxPackageType in deb rpm ; do + for linuxPackageType in deb ; do echo "${phpVersion},${linuxPackageType},${testingType},${appHostKindShortName},${testsGroup}" done done diff --git a/.ci/packer_cache.sh b/.ci/packer_cache.sh index 12b96fead..3b947b728 100755 --- a/.ci/packer_cache.sh +++ b/.ci/packer_cache.sh @@ -15,6 +15,7 @@ php:8.0-fpm php:8.1-fpm php:8.2-fpm php:8.3-fpm +php:8.4-fpm ruby:2.7.1-alpine3.12 ubuntu:20.04 " @@ -26,7 +27,7 @@ if [ -x "$(command -v docker)" ]; then # Make sure list of PHP versions supported by the Elastic APM PHP Agent is in sync. # See the comment in .ci/shared.sh - for version in 7.2 7.3 7.4 8.0 8.1 8.2 8.3 + for version in 7.2 7.3 7.4 8.0 8.1 8.2 8.3 8.4 do PHP_VERSION=${version} make -f .ci/Makefile prepare || true DOCKERFILE=Dockerfile.alpine PHP_VERSION=${version} make -f .ci/Makefile prepare || true diff --git a/.ci/shared.sh b/.ci/shared.sh index 63963943e..6d1641581 100644 --- a/.ci/shared.sh +++ b/.ci/shared.sh @@ -16,7 +16,7 @@ set -e # *) docker-compose.yml in packaging/test # -export ELASTIC_APM_PHP_TESTS_SUPPORTED_PHP_VERSIONS=(7.2 7.3 7.4 8.0 8.1 8.2 8.3) +export ELASTIC_APM_PHP_TESTS_SUPPORTED_PHP_VERSIONS=(7.2 7.3 7.4 8.0 8.1 8.2 8.3 8.4) export ELASTIC_APM_PHP_TESTS_SUPPORTED_LINUX_NATIVE_PACKAGE_TYPES=(apk deb rpm) export ELASTIC_APM_PHP_TESTS_SUPPORTED_LINUX_PACKAGE_TYPES=("${ELASTIC_APM_PHP_TESTS_SUPPORTED_LINUX_NATIVE_PACKAGE_TYPES[@]}" tar) diff --git a/.ci/snapshoty.yml b/.ci/snapshoty.yml deleted file mode 100644 index 3dcb7a0c7..000000000 --- a/.ci/snapshoty.yml +++ /dev/null @@ -1,43 +0,0 @@ ---- - -# Version of configuration to use -version: '1.0' - -# You can define a Google Cloud Account to use -account: - # Project id of the service account - project: '${GCS_PROJECT}' - # Private key id of the service account - private_key_id: '${GCS_PRIVATE_KEY_ID}' - # Private key of the service account - private_key: '${GCS_PRIVATE_KEY}' - # Email of the service account - client_email: '${GCS_CLIENT_EMAIL}' - # URI token - token_uri: 'https://oauth2.googleapis.com/token' - -# List of artifacts -artifacts: - # Path to use for artifacts discovery - - path: './build/packages' - # Files pattern to match - files_pattern: 'apm-agent-php_(?P\d+\.\d+\.\d+(-\d+)?)[\.|_](all|noarch)\.(?P\S+)' - # File layout on GCS bucket - output_pattern: '{project}/{github_branch_name}/apm-agent-php-{app_version}-{github_sha_short}.{extension}' - # List of metadata processors to use. - metadata: - # Define static custom metadata - - name: 'custom' - data: - project: 'apm-agent-php' - # Add github metadata - - name: 'jenkins' - - name: 'github_actions' - - path: './build/packages' - files_pattern: 'apm-agent-php.tar' - output_pattern: '{project}/{github_branch_name}/apm-agent-php-{github_sha_short}.tar' - metadata: - - name: 'custom' - data: - project: 'apm-agent-php' - - name: 'github_actions' diff --git a/.ci/start_external_services_for_component_tests.sh b/.ci/start_external_services_for_component_tests.sh index 6e2eb9930..ceb82ffae 100755 --- a/.ci/start_external_services_for_component_tests.sh +++ b/.ci/start_external_services_for_component_tests.sh @@ -11,7 +11,7 @@ thisScriptDir="$( dirname "${BASH_SOURCE[0]}" )" thisScriptDir="$( realpath "${thisScriptDir}" )" if [ -z "${ELASTIC_APM_PHP_TESTS_EXTERNAL_SERVICES_START_CMD}" ] ; then - export ELASTIC_APM_PHP_TESTS_EXTERNAL_SERVICES_START_CMD="docker-compose -f ${thisScriptDir}/docker-compose_external_services_for_component_tests.yml up -d" + export ELASTIC_APM_PHP_TESTS_EXTERNAL_SERVICES_START_CMD="docker compose -f ${thisScriptDir}/docker-compose_external_services_for_component_tests.yml up -d" fi run_command_with_timeout_and_retries_args=(--retry-on-error=yes) diff --git a/.ci/stop_external_services_for_component_tests.sh b/.ci/stop_external_services_for_component_tests.sh index 3dea44ecd..9977e3a87 100755 --- a/.ci/stop_external_services_for_component_tests.sh +++ b/.ci/stop_external_services_for_component_tests.sh @@ -4,4 +4,4 @@ set -xe thisScriptDir="$( dirname "${BASH_SOURCE[0]}" )" thisScriptDir="$( realpath "${thisScriptDir}" )" -docker-compose -f "${thisScriptDir}/docker-compose_external_services_for_component_tests.yml" down -v --remove-orphans +docker compose -f "${thisScriptDir}/docker-compose_external_services_for_component_tests.yml" down -v --remove-orphans diff --git a/.ci/updatecli/updatecli.d/update-gherkin-specs.yml b/.ci/updatecli/updatecli.d/update-gherkin-specs.yml deleted file mode 100644 index 4515026c3..000000000 --- a/.ci/updatecli/updatecli.d/update-gherkin-specs.yml +++ /dev/null @@ -1,82 +0,0 @@ -name: update-gherkin-specs -pipelineid: update-gherkin-specs - -scms: - default: - kind: github - spec: - user: '{{ requiredEnv "GIT_USER" }}' - email: '{{ requiredEnv "GIT_EMAIL" }}' - owner: "{{ .github.owner }}" - repository: "{{ .github.repository }}" - token: '{{ requiredEnv "GITHUB_TOKEN" }}' - username: '{{ requiredEnv "GIT_USER" }}' - branch: "{{ .github.branch }}" - apm: - kind: github - spec: - user: '{{ requiredEnv "GIT_USER" }}' - email: '{{ requiredEnv "GIT_EMAIL" }}' - owner: "{{ .github.owner }}" - repository: "{{ .github.apm_repository }}" - token: '{{ requiredEnv "GITHUB_TOKEN" }}' - username: '{{ requiredEnv "GIT_USER" }}' - branch: "{{ .github.branch }}" - -sources: - sha: - kind: file - spec: - file: 'https://github.com/{{ .github.owner }}/{{ .github.apm_repository }}/commit/{{ .github.branch }}.patch' - matchpattern: "^From\\s([0-9a-f]{40})\\s" - transformers: - - findsubmatch: - pattern: "[0-9a-f]{40}" - pull_request: - kind: shell - dependson: - - sha - spec: - command: gh api /repos/{{ .github.owner }}/{{ .github.apm_repository }}/commits/{{ source "sha" }}/pulls --jq '.[].html_url' - environments: - - name: GITHUB_TOKEN - - name: PATH - agents-gherkin-specs-tarball: - kind: shell - scmid: apm - dependson: - - sha - spec: - command: tar cvzf {{ requiredEnv "GITHUB_WORKSPACE" }}/gherkin-specs.tgz . - environments: - - name: PATH - workdir: "{{ .specs.apm_gherkin_path }}" - -actions: - pr: - kind: "github/pullrequest" - scmid: default - spec: - automerge: false - draft: false - labels: - - "automation" - description: |- - ### What - APM agent Gherkin specs automatic sync - - ### Why - *Changeset* - * {{ source "pull_request" }} - * https://github.com/elastic/apm/commit/{{ source "sha" }} - title: '[Automation] Update Gherkin specs' - -targets: - agent-gherkin-specs: - name: APM agent gherkin specs {{ source "sha" }} - scmid: default - disablesourceinput: true - kind: shell - spec: - command: 'tar -xzf {{ requiredEnv "GITHUB_WORKSPACE" }}/gherkin-specs.tgz && git --no-pager diff' - workdir: "{{ .apm_agent.gherkin_specs_path }}" diff --git a/.ci/updatecli/updatecli.d/update-json-specs.yml b/.ci/updatecli/updatecli.d/update-json-specs.yml deleted file mode 100644 index 0856990f9..000000000 --- a/.ci/updatecli/updatecli.d/update-json-specs.yml +++ /dev/null @@ -1,82 +0,0 @@ -name: update-json-specs -pipelineid: update-json-specs - -scms: - default: - kind: github - spec: - user: '{{ requiredEnv "GIT_USER" }}' - email: '{{ requiredEnv "GIT_EMAIL" }}' - owner: "{{ .github.owner }}" - repository: "{{ .github.repository }}" - token: '{{ requiredEnv "GITHUB_TOKEN" }}' - username: '{{ requiredEnv "GIT_USER" }}' - branch: "{{ .github.branch }}" - apm: - kind: github - spec: - user: '{{ requiredEnv "GIT_USER" }}' - email: '{{ requiredEnv "GIT_EMAIL" }}' - owner: "{{ .github.owner }}" - repository: "{{ .github.apm_repository }}" - token: '{{ requiredEnv "GITHUB_TOKEN" }}' - username: '{{ requiredEnv "GIT_USER" }}' - branch: "{{ .github.branch }}" - -sources: - sha: - kind: file - spec: - file: 'https://github.com/{{ .github.owner }}/{{ .github.apm_repository }}/commit/{{ .github.branch }}.patch' - matchpattern: "^From\\s([0-9a-f]{40})\\s" - transformers: - - findsubmatch: - pattern: "[0-9a-f]{40}" - pull_request: - kind: shell - dependson: - - sha - spec: - command: gh api /repos/{{ .github.owner }}/{{ .github.apm_repository }}/commits/{{ source "sha" }}/pulls --jq '.[].html_url' - environments: - - name: GITHUB_TOKEN - - name: PATH - agents-json-specs-tarball: - kind: shell - scmid: apm - dependson: - - sha - spec: - command: tar cvzf {{ requiredEnv "GITHUB_WORKSPACE" }}/json-specs.tgz . - environments: - - name: PATH - workdir: "{{ .specs.apm_json_path }}" - -actions: - pr: - kind: "github/pullrequest" - scmid: default - spec: - automerge: false - draft: false - labels: - - "automation" - description: |- - ### What - APM agent specs automatic sync - - ### Why - *Changeset* - * {{ source "pull_request" }} - * https://github.com/{{ .github.owner }}/{{ .github.apm_repository }}/commit/{{ source "sha" }} - title: '[Automation] Update JSON specs' - -targets: - agent-json-specs: - name: APM agent json specs {{ source "sha" }} - scmid: default - disablesourceinput: true - kind: shell - spec: - command: 'tar -xzf {{ requiredEnv "GITHUB_WORKSPACE" }}/json-specs.tgz && git --no-pager diff' - workdir: "{{ .apm_agent.json_specs_path }}" diff --git a/.ci/updatecli/updatecli.d/update-specs.yml b/.ci/updatecli/updatecli.d/update-specs.yml deleted file mode 100644 index be451a048..000000000 --- a/.ci/updatecli/updatecli.d/update-specs.yml +++ /dev/null @@ -1,84 +0,0 @@ -name: update-specs -pipelineid: update-schema-specs - -scms: - default: - kind: github - spec: - user: '{{ requiredEnv "GIT_USER" }}' - email: '{{ requiredEnv "GIT_EMAIL" }}' - owner: "{{ .github.owner }}" - repository: "{{ .github.repository }}" - token: '{{ requiredEnv "GITHUB_TOKEN" }}' - username: '{{ requiredEnv "GIT_USER" }}' - branch: "{{ .github.branch }}" - - apm-data: - kind: github - spec: - user: '{{ requiredEnv "GIT_USER" }}' - email: '{{ requiredEnv "GIT_EMAIL" }}' - owner: "{{ .github.owner }}" - repository: "{{ .github.apm_data_repository }}" - token: '{{ requiredEnv "GITHUB_TOKEN" }}' - username: '{{ requiredEnv "GIT_USER" }}' - branch: "{{ .github.branch }}" - -sources: - sha: - kind: file - spec: - file: 'https://github.com/{{ .github.owner }}/{{ .github.apm_data_repository }}/commit/{{ .github.branch }}.patch' - matchpattern: "^From\\s([0-9a-f]{40})\\s" - transformers: - - findsubmatch: - pattern: "[0-9a-f]{40}" - pull_request: - kind: shell - dependson: - - sha - spec: - command: gh api /repos/{{ .github.owner }}/{{ .github.apm_data_repository }}/commits/{{ source "sha" }}/pulls --jq '.[].html_url' - environments: - - name: GITHUB_TOKEN - - name: PATH - agent-specs-tarball: - kind: shell - scmid: apm-data - dependson: - - sha - spec: - command: tar cvzf {{ requiredEnv "GITHUB_WORKSPACE" }}/json-schema.tgz . - environments: - - name: PATH - workdir: "{{ .specs.apm_data_path }}" - -actions: - pr: - kind: "github/pullrequest" - scmid: default - sourceid: sha - spec: - automerge: false - draft: false - labels: - - "automation" - description: |- - ### What - APM agent json server schema automatic sync - - ### Why - *Changeset* - * {{ source "pull_request" }} - * https://github.com/{{ .github.owner }}/{{ .github.apm_data_repository }}/commit/{{ source "sha" }} - title: '[Automation] Update JSON schema specs' - -targets: - agent-json-schema: - name: APM agent json server schema {{ source "sha" }} - scmid: default - disablesourceinput: true - kind: shell - spec: - command: 'tar -xzf {{ requiredEnv "GITHUB_WORKSPACE" }}/json-schema.tgz && git --no-pager diff' - workdir: "{{ .apm_agent.server_schema_specs_path }}" diff --git a/.ci/updatecli/values.d/apm-data-spec.yml b/.ci/updatecli/values.d/apm-data-spec.yml new file mode 100644 index 000000000..7ed8ea07c --- /dev/null +++ b/.ci/updatecli/values.d/apm-data-spec.yml @@ -0,0 +1 @@ +apm_schema_specs_path: tests/APM_Server_intake_API_schema/latest_used \ No newline at end of file diff --git a/.ci/updatecli/values.d/apm-gherkin.yml b/.ci/updatecli/values.d/apm-gherkin.yml new file mode 100644 index 000000000..371bb95b5 --- /dev/null +++ b/.ci/updatecli/values.d/apm-gherkin.yml @@ -0,0 +1 @@ +apm_gherkin_specs_path: tests/APM_Agents_shared/gherkin-specs \ No newline at end of file diff --git a/.ci/updatecli/values.d/apm-json-specs.yml b/.ci/updatecli/values.d/apm-json-specs.yml new file mode 100644 index 000000000..b427f678b --- /dev/null +++ b/.ci/updatecli/values.d/apm-json-specs.yml @@ -0,0 +1 @@ +apm_json_specs_path: tests/APM_Agents_shared/json-specs \ No newline at end of file diff --git a/.ci/updatecli/values.d/scm.yml b/.ci/updatecli/values.d/scm.yml new file mode 100644 index 000000000..c92fdbb8e --- /dev/null +++ b/.ci/updatecli/values.d/scm.yml @@ -0,0 +1,10 @@ +scm: + enabled: true + owner: elastic + repository: apm-agent-php + branch: main + commitusingapi: true + # begin update-compose policy values + user: obltmachine + email: obltmachine@users.noreply.github.com + # end update-compose policy values diff --git a/.ci/updatecli/values.d/update-compose.yml b/.ci/updatecli/values.d/update-compose.yml new file mode 100644 index 000000000..02df609f2 --- /dev/null +++ b/.ci/updatecli/values.d/update-compose.yml @@ -0,0 +1,3 @@ +spec: + files: + - "updatecli-compose.yaml" \ No newline at end of file diff --git a/.ci/updatecli/values.yml b/.ci/updatecli/values.yml deleted file mode 100644 index e4e37b1a0..000000000 --- a/.ci/updatecli/values.yml +++ /dev/null @@ -1,14 +0,0 @@ -github: - owner: "elastic" - repository: "apm-agent-php" - apm_repository: "apm" - apm_data_repository: "apm-data" - branch: "main" -specs: - apm_data_path: "input/elasticapm/docs/spec/v2" - apm_json_path: "tests/agents/json-specs" - apm_gherkin_path: "tests/agents/gherkin-specs" -apm_agent: - gherkin_specs_path: "tests/APM_Agents_shared/gherkin-specs" - json_specs_path: "tests/APM_Agents_shared/json-specs" - server_schema_specs_path: "tests/APM_Server_intake_API_schema/latest_used" \ No newline at end of file diff --git a/.github/workflows/README.md b/.github/workflows/README.md index 443e1238e..0620d9abc 100644 --- a/.github/workflows/README.md +++ b/.github/workflows/README.md @@ -7,9 +7,6 @@ There are four main stages that run on GitHub actions: * Test * Package -Some other stages run for every push on the main branches: -* [Snapshoty](./snapshoty.yml) - ### Scenarios * Tests should be triggered on branch, tag, and PR basis. diff --git a/.github/workflows/addToProject.yml b/.github/workflows/addToProject.yml index bbba9ea7d..0f86393b5 100644 --- a/.github/workflows/addToProject.yml +++ b/.github/workflows/addToProject.yml @@ -5,14 +5,27 @@ on: types: [opened, edited, milestoned] pull_request_target: types: [opened, edited, milestoned] -env: - MY_GITHUB_TOKEN: ${{ secrets.APM_TECH_USER_TOKEN }} + +permissions: + contents: read jobs: assign_one_project: runs-on: ubuntu-latest name: Assign milestoned to Project steps: + - name: Get token + id: get_token + uses: tibdex/github-app-token@3beb63f4bd073e61482598c45c71c1019b59b73a # v2.1.0 + with: + app_id: ${{ secrets.OBS_AUTOMATION_APP_ID }} + private_key: ${{ secrets.OBS_AUTOMATION_APP_PEM }} + permissions: >- + { + "issues": "read", + "organization_projects": "write", + "pull_requests": "read" + } - name: Assign issues with milestones to project uses: elastic/assign-one-project-github-action@1.2.2 if: github.event.issue && github.event.issue.milestone @@ -20,6 +33,8 @@ jobs: project: 'https://github.com/orgs/elastic/projects/454' project_id: '5882982' column_name: 'Planned' + env: + MY_GITHUB_TOKEN: ${{ steps.get_token.outputs.token }} - name: Assign new pull requests to project uses: elastic/assign-one-project-github-action@1.2.2 if: github.event.action == 'opened' && github.event.pull_request @@ -27,3 +42,5 @@ jobs: project: 'https://github.com/orgs/elastic/projects/454' project_id: '5882982' column_name: 'In Progress' + env: + MY_GITHUB_TOKEN: ${{ steps.get_token.outputs.token }} diff --git a/.github/workflows/build-arch-matrix-generator.yml b/.github/workflows/build-arch-matrix-generator.yml new file mode 100644 index 000000000..c44d84198 --- /dev/null +++ b/.github/workflows/build-arch-matrix-generator.yml @@ -0,0 +1,44 @@ +--- + +# Runs the build based on the provided files in test.yml +name: build + +on: + workflow_call: + inputs: + build_arch: + required: true + type: string + default: 'x86_64' + outputs: + matrix-combinations: + description: "Matrix of architectures to build for" + value: ${{ jobs.setup-build-matrix.outputs.matrix-combinations }} + +jobs: + setup-build-matrix: + name: setup-build-matrix + runs-on: ubuntu-latest + env: + SELECTED_ARCH: ${{ inputs.build_arch }} + outputs: + matrix-combinations: ${{ steps.setup-matrix-combinations.outputs.matrix-combinations }} + steps: + - name: Create build matrix + id: setup-matrix-combinations + run: | + MATRIX='' + if [ "${SELECTED_ARCH}" == "x86_64" ] || [ "${SELECTED_ARCH}" == "all" ]; then + echo "${SELECTED_ARCH} selected. Adding x86_64" + MATRIX+='{"arch": "linux-x86-64"}, {"arch": "linuxmusl-x86-64"}' + fi + if [ "${SELECTED_ARCH}" == "arm64" ] || [ "${SELECTED_ARCH}" == "all" ]; then + echo "${SELECTED_ARCH} selected. Adding arm64" + if [ ! -z "${MATRIX}" ]; then + MATRIX+=',' + fi + MATRIX+='{"arch": "linux-arm64", "run_qemu": 1},{"arch": "linuxmusl-arm64", "run_qemu": 1}' + fi + echo "matrix-combinations={\"include\":[$MATRIX]}" + + echo "matrix-combinations={\"include\":[$MATRIX]}" >> $GITHUB_OUTPUT diff --git a/.github/workflows/build-packages.yml b/.github/workflows/build-packages.yml index 94f6e8758..4f4fb8971 100644 --- a/.github/workflows/build-packages.yml +++ b/.github/workflows/build-packages.yml @@ -1,31 +1,47 @@ --- - # Runs the build-packages based on the provided files in test.yml name: build-packages on: workflow_call: ~ +permissions: + contents: read + jobs: build-packages: runs-on: ubuntu-latest + permissions: + packages: read + strategy: + fail-fast: false + matrix: + BUILD_ARCH: ["x86-64", "arm64"] + env: + BUILD_ARCH: ${{ matrix.BUILD_ARCH }} steps: - uses: actions/checkout@v4 - - uses: actions/download-artifact@v3 + - uses: actions/download-artifact@v4 + with: + name: package-parts-linux-${{ matrix.BUILD_ARCH }} + path: agent/native/_build/linux-${{ matrix.BUILD_ARCH }}-release/ + - uses: actions/download-artifact@v4 with: - name: package-parts-linux-x86-64 - path: agent/native/_build/linux-x86-64-release/ - - uses: actions/download-artifact@v3 + name: package-parts-linuxmusl-${{ matrix.BUILD_ARCH }} + path: agent/native/_build/linuxmusl-${{ matrix.BUILD_ARCH }}-release/ + - name: Log in to the Container registry + uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3.3.0 with: - name: package-parts-linuxmusl-x86-64 - path: agent/native/_build/linuxmusl-x86-64-release/ + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} - name: package run: make -C packaging package - name: package info run: make -C packaging info - - uses: actions/upload-artifact@v3 + - uses: actions/upload-artifact@v4 with: - name: package + name: packages-${{ matrix.BUILD_ARCH }} path: | build/packages/* !build/packages/**/*.sha512 diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 97e20b16a..6e4aba9cf 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -4,26 +4,51 @@ name: build on: - workflow_call: ~ + workflow_call: + inputs: + build_arch: + required: false + type: string + default: 'x86_64' + workflow_dispatch: + inputs: + build_arch: + type: choice + description: Build architecture + default: 'x86_64' + options: + - all + - x86_64 + - arm64 + +permissions: + contents: read jobs: + setup-build-matrix: + uses: ./.github/workflows/build-arch-matrix-generator.yml + with: + build_arch: ${{ inputs.build_arch }} + build: name: build-agent-library - runs-on: ubuntu-latest - timeout-minutes: 30 + runs-on: ${{ matrix.run_qemu && 'observability-linux-2-arm64' || 'ubuntu-latest' }} + needs: setup-build-matrix + timeout-minutes: 300 strategy: fail-fast: false - matrix: - arch: - - "linux-x86-64" - - "linuxmusl-x86-64" + matrix: ${{ fromJson(needs.setup-build-matrix.outputs.matrix-combinations) }} env: BUILD_ARCHITECTURE: ${{ matrix.arch }} steps: - uses: actions/checkout@v4 - name: Build - run: make -f .ci/Makefile build - - uses: actions/upload-artifact@v3 + run: | + uname -a + echo "Detected CPUs: $(nproc)" + make -f .ci/Makefile build + + - uses: actions/upload-artifact@v4 with: name: package-parts-${{ matrix.arch }} path: | diff --git a/.github/workflows/cache.yml b/.github/workflows/cache.yml new file mode 100644 index 000000000..0323df425 --- /dev/null +++ b/.github/workflows/cache.yml @@ -0,0 +1,87 @@ +--- +name: Build Cached Container Images +on: + workflow_dispatch: + schedule: + - cron: "0 3 * * 1-5" + +env: + DOCKER_BUILDKIT: 1 + IMAGE_NAME: ${{ github.repository }} + REGISTRY: ghcr.io + +permissions: + contents: read + +jobs: + build: + uses: ./.github/workflows/build.yml + + publish: + runs-on: ubuntu-latest + needs: + - build + permissions: + packages: write + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - uses: actions/download-artifact@v4 + with: + name: package-parts-linux-x86-64 + path: agent/native/_build/linux-x86-64-release/ + + - uses: actions/download-artifact@v4 + with: + name: package-parts-linuxmusl-x86-64 + path: agent/native/_build/linuxmusl-x86-64-release/ + + - name: Create a unique tag + run: | + echo "TEST_TAG=$(date +%s)" >> "${GITHUB_ENV}" + + - name: Extract metadata (tags, labels) for Test Container + id: test-meta + uses: docker/metadata-action@369eb591f429131d6889c46b94e711f089e6ca96 # v5.6.1 + with: + images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} + tags: | + type=raw,value=${{ env.TEST_TAG }} + + - name: Build and export Container image + uses: docker/build-push-action@67a2d409c0a876cbe6b11854e3e25193efe4e62d # v6.12.0 + with: + context: ./packaging/cache/ + load: true + tags: ${{ steps.test-meta.outputs.tags }} + labels: ${{ steps.test-meta.outputs.labels }} + + - name: Package + run: make IMAGE_TAG=${{ env.TEST_TAG }} -C packaging package + + - name: Package info + run: make IMAGE_TAG=${{ env.TEST_TAG }} -C packaging info + + - name: Extract metadata (tags, labels) for Container + id: meta + uses: docker/metadata-action@369eb591f429131d6889c46b94e711f089e6ca96 # v5.6.1 + with: + images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} + tags: | + type=raw,value=latest + + - name: Log in to the Container registry + uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3.3.0 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Build and push Docker image + uses: docker/build-push-action@67a2d409c0a876cbe6b11854e3e25193efe4e62d # v6.12.0 + with: + context: ./packaging/cache/ + push: true + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} diff --git a/.github/workflows/generate-matrix.yml b/.github/workflows/generate-matrix.yml index e194b85e6..c5051be7b 100644 --- a/.github/workflows/generate-matrix.yml +++ b/.github/workflows/generate-matrix.yml @@ -10,6 +10,9 @@ on: description: "The matrix" value: ${{ jobs.generate-matrix.outputs.include }} +permissions: + contents: read + jobs: generate-matrix: timeout-minutes: 5 diff --git a/.github/workflows/labeler.yml b/.github/workflows/labeler.yml index 950c18329..744c44bbe 100644 --- a/.github/workflows/labeler.yml +++ b/.github/workflows/labeler.yml @@ -5,6 +5,11 @@ on: pull_request_target: types: [opened] +permissions: + contents: read + issues: write + pull-requests: write + jobs: triage: runs-on: ubuntu-latest diff --git a/.github/workflows/loop.yml b/.github/workflows/loop.yml index a5b7169d8..871f45137 100644 --- a/.github/workflows/loop.yml +++ b/.github/workflows/loop.yml @@ -23,6 +23,7 @@ jobs: - "8.1" - "8.2" - "8.3" + - "8.4" dockerfile: - "Dockerfile" - "Dockerfile.alpine" diff --git a/.github/workflows/opentelemetry.yml b/.github/workflows/opentelemetry.yml deleted file mode 100644 index 84a6209ff..000000000 --- a/.github/workflows/opentelemetry.yml +++ /dev/null @@ -1,22 +0,0 @@ ---- -# Look up results at https://ela.st/oblt-ci-cd-stats. -# There will be one service per GitHub repository, including the org name, and one Transaction per Workflow. -name: OpenTelemetry Export Trace - -on: - workflow_run: - workflows: [ "*" ] - types: [completed] - -permissions: - contents: read - -jobs: - otel-export-trace: - runs-on: ubuntu-latest - steps: - - uses: elastic/apm-pipeline-library/.github/actions/opentelemetry@current - with: - vaultUrl: ${{ secrets.VAULT_ADDR }} - vaultRoleId: ${{ secrets.VAULT_ROLE_ID }} - vaultSecretId: ${{ secrets.VAULT_SECRET_ID }} diff --git a/.github/workflows/phpt.yml b/.github/workflows/phpt.yml index 808d6bb27..7cd3bc569 100644 --- a/.github/workflows/phpt.yml +++ b/.github/workflows/phpt.yml @@ -28,6 +28,7 @@ jobs: - "8.1" - "8.2" - "8.3" + - "8.4" steps: # - uses: actions/checkout@v4 # - name: Fetch and extract latest release of apm-agent-php @@ -38,7 +39,7 @@ jobs: # tar --wildcards --strip-components 5 -xf /tmp/apm-agent-php-linux-x86-64.tar ./opt/elastic/apm-agent-php/extensions/elastic_apm* # cd - - uses: actions/checkout@v4 - - uses: actions/download-artifact@v3 + - uses: actions/download-artifact@v4 with: name: package-parts-linux-x86-64 path: | @@ -50,10 +51,10 @@ jobs: VERSION=${{ matrix.php-version }} VERSION_SHORT=${VERSION//./} cd agent/extension_phpt_test - docker-compose build elasticsearch apm-server phpt_${VERSION_SHORT} - docker-compose up -d elasticsearch apm-server - docker-compose run phpt_${VERSION_SHORT} - docker-compose stop + docker compose build elasticsearch apm-server phpt_${VERSION_SHORT} + docker compose up -d elasticsearch apm-server + docker compose run phpt_${VERSION_SHORT} + docker compose stop # exit 0 - if: success() || failure() name: Change results ownership @@ -62,7 +63,7 @@ jobs: sudo chmod -R 777 agent/extension_phpt_test/results sudo chown -R $(id -u):$(id -g) agent/extension_phpt_test/results - if: success() || failure() - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 continue-on-error: true with: name: phpt-test-results-${{ matrix.php-version }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 9f2935b1c..8f874d9f3 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -5,8 +5,11 @@ permissions: contents: read on: + workflow_dispatch: ~ push: tags: [ "v[0-9]+*" ] + branches: + - main env: BUILD_PACKAGES: build/packages @@ -15,20 +18,24 @@ jobs: release-started: runs-on: ubuntu-latest + if: startsWith(github.ref, 'refs/tags') steps: - - uses: elastic/apm-pipeline-library/.github/actions/slack-message@current + - uses: elastic/oblt-actions/slack/send@v1 with: - url: ${{ secrets.VAULT_ADDR }} - roleId: ${{ secrets.VAULT_ROLE_ID }} - secretId: ${{ secrets.VAULT_SECRET_ID }} - channel: "#apm-agent-php" + bot-token: ${{ secrets.SLACK_BOT_TOKEN }} + channel-id: "#apm-agent-php" message: | :runner: [${{ github.repository }}] Release *${{ github.ref_name }}* has been triggered : (<${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}|here> for further details) build: uses: ./.github/workflows/build.yml + with: + build_arch: all build-packages: + permissions: + contents: read + packages: read needs: - build uses: ./.github/workflows/build-packages.yml @@ -39,63 +46,82 @@ jobs: - build-packages env: BUCKET_NAME: "apm-agent-php" + permissions: + attestations: write + id-token: write + contents: write steps: - uses: actions/checkout@v4 - - uses: actions/download-artifact@v3 + + - name: Download package artifacts + uses: actions/download-artifact@v4 with: - name: package + pattern: packages-* path: ${{ env.BUILD_PACKAGES }} + - name: Moving packages out of folders + run: | + pushd ${{ env.BUILD_PACKAGES }} + find . -mindepth 2 -type f -exec mv -t . {} + + find . -mindepth 1 -maxdepth 1 -type d -exec rm -r {} + + popd + ls -R ${{ env.BUILD_PACKAGES }} + + - name: generate build provenance + uses: actions/attest-build-provenance@7668571508540a607bdfd90a87a560489fe372eb # v2.1.0 + with: + subject-path: "${{ github.workspace }}/${{ env.BUILD_PACKAGES }}/*" ## NOTE: The name of the zip should match the name of the folder to be zipped. - name: Prepare packages to be signed run: zip -r packages.zip packages/ working-directory: build - - name: 'Get service account' - uses: hashicorp/vault-action@v2.7.4 + - uses: elastic/oblt-actions/google/auth@v1 with: - url: ${{ secrets.VAULT_ADDR }} - roleId: ${{ secrets.VAULT_ROLE_ID }} - secretId: ${{ secrets.VAULT_SECRET_ID }} - method: approle - secrets: | - secret/observability-team/ci/apm-agent-php-bucket service-account | SERVICE_ACCOUNT ; - - - name: 'Authenticate to Google Cloud' - uses: 'google-github-actions/auth@v1' - with: - credentials_json: '${{ env.SERVICE_ACCOUNT }}' + project-number: '911195782929' - id: 'upload-file' - uses: 'google-github-actions/upload-cloud-storage@v1' + uses: 'google-github-actions/upload-cloud-storage@v2' with: path: "${{ env.BUILD_PACKAGES }}.zip" destination: "${{ env.BUCKET_NAME }}/${{ github.run_id }}" - predefinedAcl: "publicRead" - - id: buildkite - name: Run buildkite pipeline - uses: elastic/apm-pipeline-library/.github/actions/buildkite@current + - id: buildkite-run + uses: elastic/oblt-actions/buildkite/run@v1 with: - vaultUrl: ${{ secrets.VAULT_ADDR }} - vaultRoleId: ${{ secrets.VAULT_ROLE_ID }} - vaultSecretId: ${{ secrets.VAULT_SECRET_ID }} + token: ${{ secrets.BUILDKITE_TOKEN }} pipeline: observability-robots-php-release - triggerMessage: "${{ github.repository }}@${{ github.ref_name }} - sign artifacts" - waitFor: true - printBuildLogs: true - artifactName: signed-artifacts - artifactPath: "signed-artifacts.zip" - buildEnvVars: | + wait-for: true + env-vars: | BUNDLE_URL=https://storage.googleapis.com/${{ env.BUCKET_NAME }}/${{ steps.upload-file.outputs.uploaded }} + - uses: elastic/oblt-actions/buildkite/download-artifact@v1 + with: + build-number: ${{ steps.buildkite-run.outputs.number }} + path: signed-artifacts.zip + pipeline: ${{ steps.buildkite-run.outputs.pipeline }} + token: ${{ secrets.BUILDKITE_TOKEN }} + + # this artifact name is used also in some other places, + # such as ./.github/workflows/test-packages.yml. + # Therefore v4 cannot be used at the moment. + - uses: actions/upload-artifact@v4 + with: + name: signed-artifacts + path: signed-artifacts.zip + generate-test-packages-matrix: + if: startsWith(github.ref, 'refs/tags') uses: ./.github/workflows/generate-matrix.yml test-packages: + if: startsWith(github.ref, 'refs/tags') needs: - sign - generate-test-packages-matrix + permissions: + contents: read + packages: read uses: ./.github/workflows/test-packages.yml with: include: ${{ needs.generate-test-packages-matrix.outputs.include }} @@ -114,7 +140,7 @@ jobs: steps: - uses: actions/checkout@v4 - - uses: actions/download-artifact@v3 + - uses: actions/download-artifact@v4 with: name: signed-artifacts path: ${{ env.BUILD_PACKAGES }} @@ -126,12 +152,15 @@ jobs: PACKAGE_FILE: "signed-artifacts.zip" - name: Create draft release + if: startsWith(github.ref, 'refs/tags') run: make -f .ci/Makefile draft-release - name: Verify draft release + if: startsWith(github.ref, 'refs/tags') run: ORIGINAL_PACKAGES_LOCATION=${{ env.BUILD_PACKAGES }} make -f .ci/Makefile download-verify - name: Publish release + if: startsWith(github.ref, 'refs/tags') run: make -f .ci/Makefile github-release-ready notify: @@ -146,14 +175,13 @@ jobs: runs-on: ubuntu-latest steps: - id: check - uses: elastic/apm-pipeline-library/.github/actions/check-dependent-jobs@current + uses: elastic/oblt-actions/check-dependent-jobs@v1 with: - needs: ${{ toJSON(needs) }} - - uses: elastic/apm-pipeline-library/.github/actions/notify-build-status@current + jobs: ${{ toJSON(needs) }} + - if: startsWith(github.ref, 'refs/tags') + uses: elastic/oblt-actions/slack/notify-result@v1 with: + bot-token: ${{ secrets.SLACK_BOT_TOKEN }} + channel-id: "#apm-agent-php" status: ${{ steps.check.outputs.status }} - vaultUrl: ${{ secrets.VAULT_ADDR }} - vaultRoleId: ${{ secrets.VAULT_ROLE_ID }} - vaultSecretId: ${{ secrets.VAULT_SECRET_ID }} - slackChannel: "#apm-agent-php" message: "[${{ github.repository }}] Release (<${{ github.server_url }}/${{ github.repository }}/releases/tag/${{ github.ref_name }}|${{ github.ref_name }}>)" diff --git a/.github/workflows/snapshoty.yml b/.github/workflows/snapshoty.yml deleted file mode 100644 index 8cfae1152..000000000 --- a/.github/workflows/snapshoty.yml +++ /dev/null @@ -1,32 +0,0 @@ -name: snapshoty - -on: - workflow_run: - workflows: - - test - types: - - completed - branches: - - main - -jobs: - publish: - if: ${{ github.event.workflow_run.conclusion == 'success' }} - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - name: Download artifacts - run: > - gh run download ${{ github.event.workflow_run.id }} - --name package - --repo "${GITHUB_REPOSITORY}" - --dir build/packages - env: - GH_TOKEN: ${{ github.token }} - - name: Publish snapshot - uses: elastic/apm-pipeline-library/.github/actions/snapshoty-simple@current - with: - config: '.ci/snapshoty.yml' - vaultUrl: ${{ secrets.VAULT_ADDR }} - vaultRoleId: ${{ secrets.VAULT_ROLE_ID }} - vaultSecretId: ${{ secrets.VAULT_SECRET_ID }} diff --git a/.github/workflows/test-packages.yml b/.github/workflows/test-packages.yml index 58381a5b9..5dd3deb91 100644 --- a/.github/workflows/test-packages.yml +++ b/.github/workflows/test-packages.yml @@ -1,5 +1,4 @@ --- - # Runs the test packages based on the provided files in test.yml name: test-packages @@ -10,16 +9,18 @@ on: required: true type: string max-parallel: - description: 'Set the maximum number of jobs that can run simultaneously in the matrix' + description: "Set the maximum number of jobs that can run simultaneously in the matrix" default: 20 required: false type: number package-name: - description: 'The artifact name with the binaries to be tested' - default: "package" + description: "The artifact name with the binaries to be tested" required: false type: string +permissions: + contents: read + jobs: test-packages: timeout-minutes: 120 @@ -35,14 +36,32 @@ jobs: TESTING_TYPE: ${{ matrix.item[2] }} ELASTIC_APM_PHP_TESTS_MATRIX_ROW: "${{ join(matrix.item, ',') }}" BUILD_PACKAGES: build/packages + permissions: + contents: read + packages: read steps: - uses: actions/checkout@v4 - - uses: actions/download-artifact@v3 + - uses: actions/download-artifact@v4 + if: ${{ inputs.package-name }} with: name: ${{ inputs.package-name }} path: ${{ env.BUILD_PACKAGES }} + - uses: actions/download-artifact@v4 + if: ${{ ! inputs.package-name }} + with: + pattern: 'package*' + merge-multiple: true + path: ${{ env.BUILD_PACKAGES }} + + - name: Log in to the Container registry + uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3.3.0 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + ## This will help with preparing the signed artifacts that were bundled in a zip file - if: ${{ inputs.package-name == 'signed-artifacts' }} name: Unzip signed packages @@ -51,16 +70,21 @@ jobs: env: PACKAGE_FILE: "${{ inputs.package-name }}.zip" - - uses: actions/download-artifact@v3 + - uses: actions/download-artifact@v4 with: name: package-parts-linux-x86-64 path: agent/native/_build/linux-x86-64-release - - uses: actions/download-artifact@v3 + - uses: actions/download-artifact@v4 with: name: package-parts-linuxmusl-x86-64 path: agent/native/_build/linuxmusl-x86-64-release + - name: Remove ARM64 artifacts + run: | + rm ${BUILD_PACKAGES}/apm-agent-php*arm64* + rm ${BUILD_PACKAGES}/apm-agent-php*aarch64* + - if: ${{ env.TESTING_TYPE == 'lifecycle' }} name: lifecycle test run: | @@ -100,9 +124,9 @@ jobs: \; - if: success() || failure() - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: - name: test-results + name: test-results-${{ env.ELASTIC_APM_PHP_TESTS_MATRIX_ROW }} path: build/*junit.xml if-no-files-found: error @@ -116,8 +140,8 @@ jobs: find . -name "syslog" -exec bash -c 'cp {} "../syslog-files/${ELASTIC_APM_PHP_TESTS_MATRIX_ROW//[^[:alnum:]]/-}-$(basename {})"' \; find . -name "messages" -exec bash -c 'cp {} "../syslog-files/${ELASTIC_APM_PHP_TESTS_MATRIX_ROW//[^[:alnum:]]/-}-$(basename {})"' \; - if: failure() - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 continue-on-error: true with: - name: syslogs + name: syslogs-${{ env.ELASTIC_APM_PHP_TESTS_MATRIX_ROW }} path: build/syslog-files/ diff --git a/.github/workflows/test-reporter.yml b/.github/workflows/test-reporter.yml index 5e90d4559..b5b5094a5 100644 --- a/.github/workflows/test-reporter.yml +++ b/.github/workflows/test-reporter.yml @@ -9,13 +9,18 @@ on: - test types: [completed] +permissions: + contents: read + actions: read + checks: write + jobs: report: runs-on: ubuntu-latest steps: - - uses: elastic/apm-pipeline-library/.github/actions/test-report@current + - uses: elastic/oblt-actions/test-report@v1 with: - artifact: test-results # artifact name - name: test-report # Name of the check run which will be created + artifact: /test-results(.*)/ # artifact name pattern + name: 'Test Report $1' # Name of the check run which will be created path: "**/*.xml" # Path to test results (inside artifact .zip) reporter: java-junit # Format of test results diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 2132bcaac..fd056f607 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -1,6 +1,12 @@ name: test on: + workflow_call: + inputs: + build_arch: + required: true + type: string + default: 'all' pull_request: paths-ignore: - "**/*.asciidoc" @@ -26,13 +32,21 @@ concurrency: jobs: build: uses: ./.github/workflows/build.yml + with: + build_arch: all + + setup-build-matrix: + uses: ./.github/workflows/build-arch-matrix-generator.yml + with: + build_arch: all phpt-tests: name: phpt-tests - runs-on: ubuntu-latest needs: - build - timeout-minutes: 10 + - setup-build-matrix + runs-on: ${{ matrix.data.run_qemu && 'observability-linux-2-arm64' || 'ubuntu-latest' }} + timeout-minutes: 120 strategy: fail-fast: false matrix: @@ -44,25 +58,24 @@ jobs: - "8.1" - "8.2" - "8.3" - arch: - - "linux-x86-64" - - "linuxmusl-x86-64" + - "8.4" + data: ${{ fromJson(needs.setup-build-matrix.outputs.matrix-combinations).include }} env: PHP_VERSION: ${{ matrix.php-version }} - BUILD_ARCHITECTURE: ${{ matrix.arch }} + BUILD_ARCHITECTURE: ${{ matrix.data.arch }} steps: - uses: actions/checkout@v4 + - uses: actions/download-artifact@v4 + with: + name: package-parts-${{ matrix.data.arch }} + path: agent/native/_build/${{ matrix.data.arch }}-release/ - name: Prepare run: make -f .ci/Makefile prepare - - uses: actions/download-artifact@v3 - with: - name: package-parts-${{ matrix.arch }} - path: agent/native/_build/${{ matrix.arch }}-release/ - - name: Display structure of downloaded files - run: ls -R - name: phpt-unit-tests - run: make -f .ci/Makefile run-phpt-tests - + run: | + tree agent/native/_build/ + uname -a + make -f .ci/Makefile run-phpt-tests static-checks-unit-tests: name: static-checks-unit-tests runs-on: ubuntu-latest @@ -78,6 +91,7 @@ jobs: - "8.1" - "8.2" - "8.3" + - "8.4" dockerfile: - "Dockerfile" - "Dockerfile.alpine" @@ -98,28 +112,36 @@ jobs: -exec bash -c 'mv {} "build/${PHP_VERSION}-${DOCKERFILE}-$(basename {})"' \; - if: success() || failure() - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: - name: test-results + name: test-results-${{ matrix.php-version }}-${{ matrix.dockerfile }} path: build/*junit.xml if-no-files-found: error build-packages: + permissions: + contents: read + packages: read needs: - build - static-checks-unit-tests - phpt-tests uses: ./.github/workflows/build-packages.yml + secrets: inherit generate-test-packages-matrix: uses: ./.github/workflows/generate-matrix.yml test-packages: + permissions: + contents: read + packages: read needs: - build-packages - generate-test-packages-matrix uses: ./.github/workflows/test-packages.yml with: include: ${{ needs.generate-test-packages-matrix.outputs.include }} + secrets: inherit # The very last job to report whether the Workflow passed. # This will act as the Branch Protection gatekeeper diff --git a/.github/workflows/updatecli.yml b/.github/workflows/updatecli.yml index 69e79893d..659f07977 100644 --- a/.github/workflows/updatecli.yml +++ b/.github/workflows/updatecli.yml @@ -9,21 +9,53 @@ permissions: contents: read jobs: - bump: + compose: runs-on: ubuntu-latest + permissions: + contents: read + packages: read steps: - uses: actions/checkout@v4 - - uses: elastic/apm-pipeline-library/.github/actions/updatecli@current + + - name: Get token + id: get_token + uses: tibdex/github-app-token@3beb63f4bd073e61482598c45c71c1019b59b73a # v2.1.0 + with: + app_id: ${{ secrets.OBS_AUTOMATION_APP_ID }} + private_key: ${{ secrets.OBS_AUTOMATION_APP_PEM }} + permissions: >- + { + "contents": "write", + "pull_requests": "write" + } + + - uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3.3.0 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - uses: elastic/oblt-actions/updatecli/run@v1 + with: + command: --experimental compose diff + # TODO: update to the latest version so the policies can work as expected. + # latest changes in the policies require to use the dependson feature. + version: "v0.88.0" + env: + GITHUB_TOKEN: ${{ steps.get_token.outputs.token }} + + - uses: elastic/oblt-actions/updatecli/run@v1 with: - vaultUrl: ${{ secrets.VAULT_ADDR }} - vaultRoleId: ${{ secrets.VAULT_ROLE_ID }} - vaultSecretId: ${{ secrets.VAULT_SECRET_ID }} - pipeline: .ci/updatecli/updatecli.d - values: .ci/updatecli/values.yml + command: --experimental compose apply + # TODO: update to the latest version so the policies can work as expected. + # latest changes in the policies require to use the dependson feature. + version: "v0.88.0" + env: + GITHUB_TOKEN: ${{ steps.get_token.outputs.token }} + - if: failure() - uses: elastic/apm-pipeline-library/.github/actions/notify-build-status@current + uses: elastic/oblt-actions/slack/send@v1 with: - vaultUrl: ${{ secrets.VAULT_ADDR }} - vaultRoleId: ${{ secrets.VAULT_ROLE_ID }} - vaultSecretId: ${{ secrets.VAULT_SECRET_ID }} - slackChannel: "#apm-agent-php" + bot-token: ${{ secrets.SLACK_BOT_TOKEN }} + channel-id: "#apm-agent-php" + message: ":traffic_cone: updatecli failed for `${{ github.repository }}@${{ github.ref_name }}`, @robots-ci please look what's going on " diff --git a/CHANGELOG.asciidoc b/CHANGELOG.asciidoc index 6455861ae..79dc41f70 100644 --- a/CHANGELOG.asciidoc +++ b/CHANGELOG.asciidoc @@ -20,6 +20,38 @@ endif::[] // Using the template above, release notes go here. // CHANGELOG_AUTOMATION_KEYWORD + +[[release-notes-v1.15.0]] +=== v1.15.0 - 2025/01/17 +==== Features + +- Add php 8.4 compatibility {pull}1255[#1255] +- Added warning log for Xdebug incompatibility {pull}1256[#1256] {pull}1257[#1257] + +[[release-notes-v1.14.1]] +=== v1.14.1 - 2024/09/05 +==== Bug fixes +* Fixed calling post hook if instrumented functions throws {pull}1223[#1223] + +[[release-notes-v1.14.0]] +=== v1.14.0 - 2024/08/28 +==== Features +* Experimental support for ARM64 architecture + +==== Bug fixes +* Fixed issue causing forked process to hang or crash {pull}1213[#1213] + +[[release-notes-v1.13.2]] +=== v1.13.2 - 2024/08/20 +==== Bug fixes +* Set various PHP engine hooks only when the relevant feature is enabled {pull}1211[#1211] + +[[release-notes-v1.13.1]] +=== v1.13.1 - 2024/07/23 +==== Features +* Fixed memory leak in exception handling {pull}1174[#1174] +* Changed exprerimental part of WordPress instrumentation that is measuring latency impact by plugin to be disabled by default {pull}1181[#1181] + [[release-notes-v1.13.0]] === v1.13.0 - 2024/01/31 ==== Features diff --git a/DEVELOPMENT.md b/DEVELOPMENT.md index af882849d..ba8f16ea1 100644 --- a/DEVELOPMENT.md +++ b/DEVELOPMENT.md @@ -130,7 +130,7 @@ If you want to update images used to build native extension, you need to go into `Dockerfile_musl` for Linux x86_64 with musl libc implementation and `Dockerfile_glibc` for all other x86_64 distros with glibc implementation. Then you need to increment image version in `docker-compose.yml`. Remember to update Dockerfiles for all architectures, if needed. To build new images, you just need to call: ```bash -docker-compose build +docker compose build ``` It will build images for all supported architectures. As a result you should get summary like this: ```bash @@ -160,7 +160,7 @@ If you want to update images used for testing, you need to go into `packaging/te Then you need to increment image version in `docker-compose.yml`.\ To build new images, you just need to call: ```bash -docker-compose build +docker compose build ``` It will build and tag images for all test scenarios. As a result you should get summary like this: ```bash diff --git a/agent/extension_phpt_test/README.md b/agent/extension_phpt_test/README.md index ed752e53b..c717724af 100644 --- a/agent/extension_phpt_test/README.md +++ b/agent/extension_phpt_test/README.md @@ -25,7 +25,7 @@ To run the tests, you must first build the agent binaries for the `linux-x86-64- ``` cd apm-agent-php/agent/extension_phpt_test -docker-compose up --build +docker compose up --build ``` It will start up elastic search and apm-server and execute tests for all supported PHP releases. @@ -38,7 +38,7 @@ In this example we will run all tests with PHP 8.2 ``` cd apm-agent-php/agent/extension_phpt_test -docker-compose up elasticsearch apm-server phpt_82 +docker compose up elasticsearch apm-server phpt_82 ``` It will start up elastic search and apm-server and execute tests for PHP 8.2. @@ -48,8 +48,8 @@ In this example we will run test with PHP 8.2 ``` cd apm-agent-php/agent/extension_phpt_test -docker-compose build phpt_82 elasticsearch apm-server -docker-compose run --rm phpt_82 /scripts/run.sh tests/output/ob_start_error_005.phpt +docker compose build phpt_82 elasticsearch apm-server +docker compose run --rm phpt_82 /scripts/run.sh tests/output/ob_start_error_005.phpt ``` ### Run bunch of tests from a folder @@ -58,8 +58,8 @@ In this example we will run test with PHP 8.2 ``` cd apm-agent-php/agent/extension_phpt_test -docker-compose build phpt_82 elasticsearch apm-server -docker-compose run --rm phpt_82 /scripts/run.sh tests/output +docker compose build phpt_82 elasticsearch apm-server +docker compose run --rm phpt_82 /scripts/run.sh tests/output ``` ### Core dump examination with gdb @@ -68,7 +68,7 @@ In order to diagnose a core dump, you need to run the image together with the mo ``` cd apm-agent-php/agent/extension_phpt_test -docker-compose run -v /path/to/coredumps:/path/to/coredumps phpt_82 /bin/bash +docker compose run -v /path/to/coredumps:/path/to/coredumps phpt_82 /bin/bash ``` then inside runnning container: ``` diff --git a/agent/extension_phpt_test/scripts/run.sh b/agent/extension_phpt_test/scripts/run.sh index db7c01f1d..7f5c4112a 100755 --- a/agent/extension_phpt_test/scripts/run.sh +++ b/agent/extension_phpt_test/scripts/run.sh @@ -1,8 +1,8 @@ #!/bin/bash # if you want to run only that one test, execute scipt with test or folder name -# example: docker-compose run --rm phpt_82 /scripts/run.sh tests/output -# example: docker-compose run --rm phpt_82 /scripts/run.sh tests/output/ob_start_error_005.phpt +# example: docker compose run --rm phpt_82 /scripts/run.sh tests/output +# example: docker compose run --rm phpt_82 /scripts/run.sh tests/output/ob_start_error_005.phpt TEST_OR_DIRECTORY_TO_EXECUTE=$1 PHP_VERSION=`php-config --version` @@ -24,7 +24,7 @@ elif [ $PHP_VERSION == "8.2" ]; then fi mkdir -m 666 -p /results/${PHP_VERSION} -echo "Making results directory /results/${PHP_VERSION} resultCode: $?" +echo "Making results directory /results/${PHP_VERSION} resultCode: $?" RESULT_PREFIX=`date -u +"%Y%m%d_%H%M%S-"` @@ -136,4 +136,4 @@ compress_test_results ${TEST_FAILED_WITH_AGENT_ARCH} --failed_with_agent ${TEST_FAILED_WITH_AGENT} --failed_without_agent ${TEST_FAILED_WITHOUT_AGENT} \ --results_with_agent ${TEST_RESULTS_WITH_AGENT} --results_without_agent ${TEST_RESULTS_WITHOUT_AGENT} \ --segfaults_with_agent ${TEST_SEGFAULTS_WITH_AGENT} --segfaults_without_agent ${TEST_SEGFAULTS_WITHOUT_AGENT} \ - --markdown ${TEST_RESULTS_MD} \ No newline at end of file + --markdown ${TEST_RESULTS_MD} diff --git a/agent/native/CMakeLists.txt b/agent/native/CMakeLists.txt index 2758c03c8..22252ec7b 100644 --- a/agent/native/CMakeLists.txt +++ b/agent/native/CMakeLists.txt @@ -43,7 +43,7 @@ include(elastic_conan_debugsymbols) # Install project dependencies -set(_supported_php_versions 72 73 74 80 81 82 83) +set(_supported_php_versions 72 73 74 80 81 82 83 84) function(get_php_api_from_release php_version ret_val) block(SCOPE_FOR VARIABLES) @@ -54,6 +54,7 @@ function(get_php_api_from_release php_version ret_val) set(_php_release_81 20210902) set(_php_release_82 20220829) set(_php_release_83 20230831) + set(_php_release_84 20240924) set(${ret_val} ${_php_release_${php_version}}) return(PROPAGATE ${ret_val}) diff --git a/agent/native/building/dependencies/php84/conandata.yml b/agent/native/building/dependencies/php84/conandata.yml new file mode 100644 index 000000000..55abcef12 --- /dev/null +++ b/agent/native/building/dependencies/php84/conandata.yml @@ -0,0 +1,37 @@ +name: "php-headers-84" +version: "1.0" +php_source_version: 8.4.1 + +sources: + 7.2.34: + linux: + - url: "https://www.php.net/distributions/php-7.2.34.tar.gz" + contentsRoot: "php-7.2.34" + 7.3.33: + linux: + - url: "https://www.php.net/distributions/php-7.3.33.tar.gz" + contentsRoot: "php-7.3.33" + 7.4.33: + linux: + - url: "https://www.php.net/distributions/php-7.4.33.tar.gz" + contentsRoot: "php-7.4.33" + 8.0.28: + linux: + - url: "https://www.php.net/distributions/php-8.0.28.tar.gz" + contentsRoot: "php-8.0.28" + 8.1.18: + linux: + - url: "https://www.php.net/distributions/php-8.1.18.tar.gz" + contentsRoot: "php-8.1.18" + 8.2.5: + linux: + - url: "https://www.php.net/distributions/php-8.2.5.tar.gz" + contentsRoot: "php-8.2.5" + 8.3.2: + linux: + - url: "https://www.php.net/distributions/php-8.3.2.tar.gz" + contentsRoot: "php-8.3.2" + 8.4.1: + linux: + - url: "https://www.php.net/distributions/php-8.4.1.tar.gz" + contentsRoot: "php-8.4.1" diff --git a/agent/native/building/dependencies/php84/conanfile.py b/agent/native/building/dependencies/php84/conanfile.py new file mode 100644 index 000000000..2f1ec5fcc --- /dev/null +++ b/agent/native/building/dependencies/php84/conanfile.py @@ -0,0 +1,56 @@ +import os +import shutil + +from conans import tools, ConanFile, AutoToolsBuildEnvironment + +class PhpHeadersForPHP81Conan(ConanFile): + description = "PHP headers package required to build Elastic APM agent without additional PHP dependencies" + license = "The PHP License, version 3.01" + homepage = "https://php.net/" + url = "https://php.net/" + author = "pawel.filipczak@elastic.co" + + settings = "os", "compiler", "build_type", "arch" + platform = "linux" + + def init(self): + self.name = self.conan_data["name"] + self.version = self.conan_data["version"] # version of the package + self.php_version = self.conan_data["php_source_version"] # version of the PHP to build + self.source_temp_dir = "php-src" + + def requirements(self): + self.requires("libxml2/2.9.9") + self.requires("sqlite3/3.29.0") + + def source(self): + for source in self.conan_data["sources"][self.php_version][self.platform]: + + if "contentsRoot" in source: + # small hack - it can't contain custom fields, so we're removing it from source (got an unexpected keyword argument) + contentRoot = source["contentsRoot"] + del source["contentsRoot"] + tools.get(**source) + os.rename(contentRoot, self.source_temp_dir) + else: + self.output.error("Could not find 'contentsRoot' in conandata.yml") + raise Exception("Could not find 'contentsRoot' in conandata.yml") + + def build(self): + with tools.chdir(os.path.join(self.source_folder, self.source_temp_dir)): + buildEnv = AutoToolsBuildEnvironment(self) + envVariables = buildEnv.vars + envVariables['ac_cv_php_xml2_config_path'] = os.path.join(self.deps_cpp_info["libxml2"].rootpath, "bin/xml2-config") + envVariables['LIBXML_LIBS'] = os.path.join(self.deps_cpp_info["libxml2"].rootpath, self.deps_cpp_info["libxml2"].libdirs[0]) + envVariables['LIBXML_CFLAGS'] = "-I{}".format(os.path.join(self.deps_cpp_info["libxml2"].rootpath, self.deps_cpp_info["libxml2"].includedirs[0])) + envVariables['SQLITE_LIBS'] = os.path.join(self.deps_cpp_info["sqlite3"].rootpath, self.deps_cpp_info["sqlite3"].libdirs[0]) + envVariables['SQLITE_CFLAGS'] = "-I{}".format(os.path.join(self.deps_cpp_info["sqlite3"].rootpath, self.deps_cpp_info["sqlite3"].includedirs[0])) + self.run("./buildconf --force") + buildEnv.configure(args=[""], vars=envVariables, build=False, host=False) + + def package(self): + source = os.path.join(self.source_folder, self.source_temp_dir) + self.copy("*.h", src=source, dst='include', keep_path=True) + + def package_id(self): + del self.info.settings.compiler.version diff --git a/agent/native/ext/AST_debug.cpp b/agent/native/ext/AST_debug.cpp index 4794d91cd..cbe148df1 100644 --- a/agent/native/ext/AST_debug.cpp +++ b/agent/native/ext/AST_debug.cpp @@ -224,11 +224,16 @@ String zendAstKindToString( zend_ast_kind kind ) ELASTIC_APM_GEN_ENUM_TO_STRING_SWITCH_CASE( ZEND_AST_TYPE_INTERSECTION ); #endif - #if PHP_VERSION_ID >= ELASTIC_APM_BUILD_PHP_VERSION_ID( 8, 3, 0 ) /* if PHP version from 8.3.0 */ ELASTIC_APM_GEN_ENUM_TO_STRING_SWITCH_CASE( ZEND_AST_MODIFIER_LIST ); #endif + #if PHP_VERSION_ID >= ELASTIC_APM_BUILD_PHP_VERSION_ID( 8, 4, 0 ) /* if PHP version from 8.4.0 */ + ELASTIC_APM_GEN_ENUM_TO_STRING_SWITCH_CASE( ZEND_AST_PARENT_PROPERTY_HOOK_CALL ); + ELASTIC_APM_GEN_ENUM_TO_STRING_SWITCH_CASE( ZEND_AST_PROPERTY_HOOK ); + ELASTIC_APM_GEN_ENUM_TO_STRING_SWITCH_CASE( ZEND_AST_PROPERTY_HOOK_SHORT_BODY ); + #endif + default: return NULL; } diff --git a/agent/native/ext/AST_instrumentation.cpp b/agent/native/ext/AST_instrumentation.cpp index 45a096269..360f79aa2 100644 --- a/agent/native/ext/AST_instrumentation.cpp +++ b/agent/native/ext/AST_instrumentation.cpp @@ -181,25 +181,47 @@ bool isZendAstListKind( zend_ast_kind kind ) } /** - * zend_ast_create and zend_ast_create_ex allowed up to 4 child* parameters for version before PHP v8 - * and the limit was increased to 5 in PHP v8 + * Max number of children for AST nodes is + * 4 for PHP before 8.0 + * 5 for PHP from 8.0 but before 8.4 + * 6 for PHP from 8.4 * * @see ZEND_AST_SPEC_CALL_EX + * + * When adding support for a new PHP version: + * - Make sure g_astNodeMaxChildCount is correct + * - If g_astNodeMaxChildCount changed then update createAstEx() + * - Make sure zendAstKindToString() in AST_debug.cpp includes all the enum cases from enum _zend_ast_kind in /Zend/zend_ast.h + * - Increment minor part of PHP version in static_assert below */ -static size_t g_maxCreateAstChildCount = +static_assert( + PHP_VERSION_ID < ELASTIC_APM_BUILD_PHP_VERSION_ID( 8, 5, 0 ), + "Make sure g_astNodeMaxChildCount is correct. See max number of children in enum _zend_ast_kind in /Zend/zend_ast.h" +); +static constexpr size_t g_astNodeMaxChildCount = + #if PHP_VERSION_ID < ELASTIC_APM_BUILD_PHP_VERSION_ID( 8, 0, 0 ) + 4 // PHP before 8.0 + #elif PHP_VERSION_ID < ELASTIC_APM_BUILD_PHP_VERSION_ID( 8, 4, 0 ) + 5 // PHP from 8.0 but before 8.4 + #else + 6 // PHP from 8.4 + #endif +; +static constexpr size_t g_zendKindWithLargestChildNodesCount = #if PHP_VERSION_ID < ELASTIC_APM_BUILD_PHP_VERSION_ID( 8, 0, 0 ) - 4 + ZEND_AST_FOR // PHP before 8.0 #else - 5 + ZEND_AST_PARAM // PHP from 8.0 #endif ; +static_assert(g_zendKindWithLargestChildNodesCount == (g_astNodeMaxChildCount << ZEND_AST_NUM_CHILDREN_SHIFT)); zend_ast* createAstEx( zend_ast_kind kind, zend_ast_attr attr, ZendAstPtrArrayView children ) { char txtOutStreamBuf[ELASTIC_APM_TEXT_OUTPUT_STREAM_ON_STACK_BUFFER_SIZE]; TextOutputStream txtOutStream = ELASTIC_APM_TEXT_OUTPUT_STREAM_FROM_STATIC_BUFFER( txtOutStreamBuf ); - ELASTIC_APM_ASSERT_LE_UINT64( children.count, g_maxCreateAstChildCount ); + ELASTIC_APM_ASSERT_LE_UINT64( children.count, g_astNodeMaxChildCount ); ELASTIC_APM_ASSERT( ! isZendAstListKind( kind ), "kind: %s", streamZendAstKind( kind, &txtOutStream ) ); switch( children.count ) @@ -218,6 +240,10 @@ zend_ast* createAstEx( zend_ast_kind kind, zend_ast_attr attr, ZendAstPtrArrayVi case 5: return zend_ast_create_ex( kind, attr, children.values[ 0 ], children.values[ 1 ], children.values[ 2 ], children.values[ 3 ], children.values[ 4 ] ); #endif + #if PHP_VERSION_ID >= ELASTIC_APM_BUILD_PHP_VERSION_ID( 8, 4, 0 ) + case 6: + return zend_ast_create_ex( kind, attr, children.values[ 0 ], children.values[ 1 ], children.values[ 2 ], children.values[ 3 ], children.values[ 4 ], children.values[ 5 ] ); + #endif default: // silence compiler warning return nullptr; } @@ -227,9 +253,9 @@ ResultCode createAstExCheckChildrenCount( zend_ast_kind kind, zend_ast_attr attr { ResultCode resultCode; - if ( children.count > g_maxCreateAstChildCount ) + if ( children.count > g_astNodeMaxChildCount ) { - ELASTIC_APM_LOG_ERROR( "Number of children is larger than max; children.count: %u, g_maxCreateAstChildCount: %u", (unsigned)children.count, (unsigned)g_maxCreateAstChildCount ); + ELASTIC_APM_LOG_ERROR( "Number of children is larger than max; children.count: %u, g_astNodeMaxChildCount: %u", (unsigned)children.count, (unsigned)g_astNodeMaxChildCount ); ELASTIC_APM_SET_RESULT_CODE_AND_GOTO_FAILURE_EX( resultFailure ); } diff --git a/agent/native/ext/ConfigManager.cpp b/agent/native/ext/ConfigManager.cpp index b27ed5998..5fb2f0dc3 100644 --- a/agent/native/ext/ConfigManager.cpp +++ b/agent/native/ext/ConfigManager.cpp @@ -964,7 +964,7 @@ static void initOptionsMetadata( OptionMetadata* optsMeta ) buildBoolOptionMetadata, astProcessEnabled, ELASTIC_APM_CFG_OPT_NAME_AST_PROCESS_ENABLED, - /* defaultValue: */ true ); + /* defaultValue: */ false ); ELASTIC_APM_INIT_METADATA( buildBoolOptionMetadata, diff --git a/agent/native/ext/Hooking.cpp b/agent/native/ext/Hooking.cpp index f3610bf66..5b5f8208a 100644 --- a/agent/native/ext/Hooking.cpp +++ b/agent/native/ext/Hooking.cpp @@ -11,6 +11,10 @@ #include #include +#include "log.h" + +#define ELASTIC_APM_CURRENT_LOG_CATEGORY ELASTIC_APM_LOG_CATEGORY_EXT_INFRA + namespace elasticapm::php { #if PHP_VERSION_ID < 80000 @@ -88,10 +92,21 @@ static void elastic_interrupt_function(zend_execute_data *execute_data) { } zend_end_try(); } -void Hooking::replaceHooks() { +void Hooking::replaceHooks(bool cfgCaptureErrors, bool cfgInferredSpansEnabled) { + if (cfgInferredSpansEnabled) { zend_execute_internal = elastic_execute_internal; zend_interrupt_function = elastic_interrupt_function; + ELASTIC_APM_LOG_DEBUG( "Replaced zend_execute_internal and zend_interrupt_function hooks" ); + } else { + ELASTIC_APM_LOG_DEBUG( "NOT replacing zend_execute_internal and zend_interrupt_function hooks because profiling_inferred_spans_enabled configuration option is set to false" ); + } + + if (cfgCaptureErrors) { zend_error_cb = elastic_apm_error_cb; + ELASTIC_APM_LOG_DEBUG( "Replaced zend_error_cb hook" ); + } else { + ELASTIC_APM_LOG_DEBUG( "NOT replacing zend_error_cb hook because capture_errors configuration option is set to false" ); + } } } diff --git a/agent/native/ext/Hooking.h b/agent/native/ext/Hooking.h index 9afd94ce2..b25faa2e0 100644 --- a/agent/native/ext/Hooking.h +++ b/agent/native/ext/Hooking.h @@ -46,7 +46,7 @@ class Hooking { zend_error_cb = original_zend_error_cb_; } - void replaceHooks(); + void replaceHooks(bool cfgCaptureErrors, bool cfgInferredSpansEnabled); zend_execute_internal_t getOriginalExecuteInternal() { return original_execute_internal_; diff --git a/agent/native/ext/MemoryTracker.cpp b/agent/native/ext/MemoryTracker.cpp index edd08bd59..c52f4435d 100644 --- a/agent/native/ext/MemoryTracker.cpp +++ b/agent/native/ext/MemoryTracker.cpp @@ -241,6 +241,10 @@ void removeFromTrackedAllocatedBlocks( IntrusiveDoublyLinkedList* allocatedBlocks, size_t* possibleActuallyRequestedSize ) { + if (!allocatedBlock) { + return; + } + EmbeddedTrackingDataHeader* trackingDataHeader = allocatedBlockToTrackingData( allocatedBlock, originallyRequestedSize ); verifyMagic( "prefix", trackingDataHeader->prefixMagic, prefixMagicExpectedValue ); @@ -275,7 +279,7 @@ void memoryTrackerBeforeFree( IntrusiveDoublyLinkedList* allocatedBlocks = isPersistent ? &memTracker->allocatedPersistentBlocks : &memTracker->allocatedRequestScopedBlocks; ELASTIC_APM_ASSERT( *allocated >= originallyRequestedSize - , "Attempting to free more %s memory than allocated. Allocated: %" PRIu64 ". Attempting to free: %" PRIu64 + , "Attempting to free more %s memory than allocated. Allocated: %" PRIu64 ". Attempting to free: %" PRIu64 , allocType( isPersistent ), *allocated, (UInt64)originallyRequestedSize ); *possibleActuallyRequestedSize = originallyRequestedSize; diff --git a/agent/native/ext/constants.h b/agent/native/ext/constants.h index 48067fd69..fb00a8f35 100644 --- a/agent/native/ext/constants.h +++ b/agent/native/ext/constants.h @@ -19,6 +19,7 @@ #pragma once + #include "basic_macros.h" #include "elastic_apm_assert.h" diff --git a/agent/native/ext/lifecycle.cpp b/agent/native/ext/lifecycle.cpp index 94193a118..e4fb860b8 100644 --- a/agent/native/ext/lifecycle.cpp +++ b/agent/native/ext/lifecycle.cpp @@ -140,7 +140,7 @@ typedef void (* ZendThrowExceptionHook )( ); // static bool elasticApmZendErrorCallbackSet = false; -static bool elasticApmZendThrowExceptionHookSet = false; +static bool elasticApmZendThrowExceptionHookReplaced = false; static ZendThrowExceptionHook originalZendThrowExceptionHook = NULL; void resetLastThrown() { @@ -162,11 +162,10 @@ void elasticApmZendThrowExceptionHookImpl( resetLastThrown(); #if PHP_MAJOR_VERSION >= 8 /* if PHP version is 8.* and later */ - zval thrownAsZval; - zval* thrownAsPzval = &thrownAsZval; - ZVAL_OBJ_COPY( /* dst: */ thrownAsPzval, /* src: */ thrownAsPzobj ); + ZVAL_OBJ_COPY(&ELASTICAPM_G( lastException ), thrownAsPzobj ); +#else + ZVAL_COPY(&ELASTICAPM_G(lastException), thrownAsPzval ); #endif - ZVAL_COPY( /* pZvalDst: */ &ELASTICAPM_G(lastException), /* pZvalSrc: */ thrownAsPzval ); ELASTIC_APM_LOG_DEBUG_FUNCTION_EXIT(); } @@ -187,18 +186,13 @@ void elasticApmZendThrowExceptionHook( #endif ) { - Tracer* const tracer = getGlobalTracer(); - const ConfigSnapshot* config = getTracerCurrentConfigSnapshot( tracer ); - if (config->captureErrors) { - elasticApmZendThrowExceptionHookImpl( thrownObj ); - } + elasticApmZendThrowExceptionHookImpl( thrownObj ); if (originalZendThrowExceptionHook == elasticApmZendThrowExceptionHook) { ELASTIC_APM_LOG_CRITICAL( "originalZendThrowExceptionHook == elasticApmZendThrowExceptionHook" ); return; } - if ( originalZendThrowExceptionHook != NULL ) { originalZendThrowExceptionHook( thrownObj ); @@ -206,22 +200,28 @@ void elasticApmZendThrowExceptionHook( } -static void registerExceptionHooks() { - if (!elasticApmZendThrowExceptionHookSet) { - originalZendThrowExceptionHook = zend_throw_exception_hook; - zend_throw_exception_hook = elasticApmZendThrowExceptionHook; - elasticApmZendThrowExceptionHookSet = true; - ELASTIC_APM_LOG_DEBUG( "Set zend_throw_exception_hook: %p (%s elasticApmZendThrowExceptionHook) -> %p" - , originalZendThrowExceptionHook, originalZendThrowExceptionHook == elasticApmZendThrowExceptionHook ? "==" : "!=" - , elasticApmZendThrowExceptionHook ); - } else { - ELASTIC_APM_LOG_WARNING( "zend_erzend_throw_exception_hook already set: %p. Original: %p, Elastic: %p", zend_throw_exception_hook, originalZendThrowExceptionHook, elasticApmZendThrowExceptionHook ); +static void registerExceptionHooks(const ConfigSnapshot& config) { + if (!config.captureErrors) { + ELASTIC_APM_LOG_DEBUG( "NOT replacing zend_throw_exception_hook hook because capture_errors configuration option is set to false" ); + return; } + + if (elasticApmZendThrowExceptionHookReplaced) { + ELASTIC_APM_LOG_WARNING( "zend_throw_exception_hook already replaced: %p. Original: %p, Elastic: %p", zend_throw_exception_hook, originalZendThrowExceptionHook, elasticApmZendThrowExceptionHook ); + return; + } + + originalZendThrowExceptionHook = zend_throw_exception_hook; + zend_throw_exception_hook = elasticApmZendThrowExceptionHook; + elasticApmZendThrowExceptionHookReplaced = true; + ELASTIC_APM_LOG_DEBUG( "Replaced zend_throw_exception_hook: %p (%s elasticApmZendThrowExceptionHook) -> %p" + , originalZendThrowExceptionHook, originalZendThrowExceptionHook == elasticApmZendThrowExceptionHook ? "==" : "!=" + , elasticApmZendThrowExceptionHook ); } static void unregisterExceptionHooks() { - if (elasticApmZendThrowExceptionHookSet) { + if (elasticApmZendThrowExceptionHookReplaced) { ZendThrowExceptionHook zendThrowExceptionHookBeforeRestore = zend_throw_exception_hook; zend_throw_exception_hook = originalZendThrowExceptionHook; ELASTIC_APM_LOG_DEBUG( "Restored zend_throw_exception_hook: %p (%s elasticApmZendThrowExceptionHook: %p) -> %p" @@ -278,7 +278,7 @@ void elasticApmModuleInit( int moduleType, int moduleNumber ) registerCallbacksToLogFork(); registerAtExitLogging(); - registerExceptionHooks(); + registerExceptionHooks(*config); curlCode = curl_global_init( CURL_GLOBAL_ALL ); if ( curlCode != CURLE_OK ) @@ -291,10 +291,14 @@ void elasticApmModuleInit( int moduleType, int moduleNumber ) astInstrumentationOnModuleInit( config ); - elasticapm::php::Hooking::getInstance().replaceHooks(); + elasticapm::php::Hooking::getInstance().replaceHooks(config->captureErrors, config->profilingInferredSpansEnabled); if (php_check_open_basedir_ex(config->bootstrapPhpPartFile, false) != 0) { - ELASTIC_APM_LOG_WARNING( "Elastic Agent bootstrap file (%s) is located outside of paths allowed by open_basedir ini setting. Read more details here https://www.elastic.co/guide/en/apm/agent/php/current/setup.html#limitations", config->bootstrapPhpPartFile); + ELASTIC_APM_LOG_WARNING( + "Elastic Agent bootstrap file (%s) is located outside of paths allowed by open_basedir ini setting." + " For more details see https://www.elastic.co/guide/en/apm/agent/php/current/setup.html#limitation-open_basedir" + , config->bootstrapPhpPartFile + ); } resultCode = resultSuccess; @@ -410,12 +414,19 @@ void elasticApmRequestInit() enableAccessToServerGlobal(); bool preloadDetected = requestCounter == 1 ? detectOpcachePreload() : false; - if (config && config->debugDiagnosticsFile && !preloadDetected && requestCounter <= 2) { + if (!preloadDetected && requestCounter <= 2) { if (ELASTICAPM_G(globals)->sharedMemory_->shouldExecuteOneTimeTaskAmongWorkers()) { - try { - elasticapm::utils::storeDiagnosticInformation(elasticapm::utils::getParameterizedString(config->debugDiagnosticsFile), *(ELASTICAPM_G(globals)->bridge_)); - } catch (std::exception const &e) { - ELASTIC_APM_LOG_WARNING( "Unable to write agent diagnostics: %s", e.what() ); + using namespace std::string_view_literals; + if ( ELASTICAPM_G( globals )->bridge_->isExtensionLoaded( "xdebug"sv ) ) { + ELASTIC_APM_LOG_WARNING( "Xdebug is loaded, which is not supported by the Elastic APM Agent. This may lead to stability or memory issues"); + } + + if (config && config->debugDiagnosticsFile) { + try { + elasticapm::utils::storeDiagnosticInformation(elasticapm::utils::getParameterizedString(config->debugDiagnosticsFile), *(ELASTICAPM_G(globals)->bridge_)); + } catch (std::exception const &e) { + ELASTIC_APM_LOG_WARNING( "Unable to write agent diagnostics: %s", e.what() ); + } } } } @@ -573,7 +584,7 @@ void elasticApmRequestShutdown() ELASTIC_APM_LOG_DEBUG( "opcache.preload request detected on shutdown" ); return; } - + if (ELASTICAPM_G(globals)->periodicTaskExecutor_) { ELASTIC_APM_LOG_DEBUG("pausing inferred spans thread"); ELASTICAPM_G(globals)->periodicTaskExecutor_->suspendPeriodicTasks(); @@ -677,4 +688,4 @@ ResultCode elasticApmEnterAgentCode( String dbgCalledFromFile, int dbgCalledFrom failure: goto finally; -} \ No newline at end of file +} diff --git a/agent/native/ext/tests_util/tests_util.php b/agent/native/ext/tests_util/tests_util.php index 0b3a60f67..b7528ae83 100644 --- a/agent/native/ext/tests_util/tests_util.php +++ b/agent/native/ext/tests_util/tests_util.php @@ -21,7 +21,7 @@ declare(strict_types=1); -error_reporting(E_ALL | E_STRICT); +error_reporting(E_ALL); function elasticApmOnAssertFailure(string $condDesc, string $expr, $actual, $expected) { diff --git a/agent/native/ext/tracer_PHP_part.cpp b/agent/native/ext/tracer_PHP_part.cpp index c72ab4912..02d8cc73c 100644 --- a/agent/native/ext/tracer_PHP_part.cpp +++ b/agent/native/ext/tracer_PHP_part.cpp @@ -17,13 +17,13 @@ * under the License. */ -#include "tracer_PHP_part.h" -#include "log.h" +#include "ConfigSnapshot.h" +#include "Exceptions.h" #include "Tracer.h" -#include "util_for_PHP.h" #include "basic_macros.h" #include "elastic_apm_API.h" -#include "ConfigSnapshot.h" +#include "log.h" +#include "util_for_PHP.h" #define ELASTIC_APM_CURRENT_LOG_CATEGORY ELASTIC_APM_LOG_CATEGORY_C_TO_PHP @@ -267,6 +267,8 @@ void tracerPhpPartInternalFuncCallPostHook( uint32_t dbgInterceptRegistrationId, ResultCode resultCode; zval phpPartArgs[ 2 ]; + elasticapm::php::AutomaticExceptionStateRestorer restorer; + if (!canInvokeTracerPhpPart()) { if (switchTracerPhpPartStateToFailed( /* reason */ "Unexpected current tracer PHP part state", __FUNCTION__ )) { ELASTIC_APM_SET_RESULT_CODE_AND_GOTO_FAILURE(); @@ -275,6 +277,7 @@ void tracerPhpPartInternalFuncCallPostHook( uint32_t dbgInterceptRegistrationId, } } + // The first argument to PHP part's interceptedCallPostHook() is $hasExitedByException (bool) ZVAL_FALSE( &( phpPartArgs[ 0 ] ) ); diff --git a/agent/native/ext/util_for_PHP.cpp b/agent/native/ext/util_for_PHP.cpp index 450e91eca..e1a486f15 100644 --- a/agent/native/ext/util_for_PHP.cpp +++ b/agent/native/ext/util_for_PHP.cpp @@ -214,8 +214,7 @@ ResultCode callPhpFunction( StringView phpFunctionName, uint32_t argsCount, zval , args ); if ( callUserFunctionRetVal != SUCCESS ) { - ELASTIC_APM_LOG_ERROR( "call_user_function failed. Return value: %d. PHP function name: `%.*s'. argsCount: %u." - , callUserFunctionRetVal, (int) phpFunctionName.length, phpFunctionName.begin, argsCount ); + ELASTIC_APM_LOG_ERROR( "call_user_function failed. Return value: %d. PHP function name: `%.*s'. argsCount: %u. Exception: %p", callUserFunctionRetVal, (int) phpFunctionName.length, phpFunctionName.begin, argsCount, EG( exception ) ); ELASTIC_APM_SET_RESULT_CODE_AND_GOTO_FAILURE(); } diff --git a/agent/native/libcommon/code/PeriodicTaskExecutor.h b/agent/native/libcommon/code/PeriodicTaskExecutor.h index aa8e32e9a..4a4463b29 100644 --- a/agent/native/libcommon/code/PeriodicTaskExecutor.h +++ b/agent/native/libcommon/code/PeriodicTaskExecutor.h @@ -11,7 +11,6 @@ namespace elasticapm::php { - class PeriodicTaskExecutor : public ForkableInterface { private: auto getThreadWorkerFunction() { @@ -30,7 +29,6 @@ class PeriodicTaskExecutor : public ForkableInterface { ~PeriodicTaskExecutor() { shutdown(); - if (thread_.joinable()) { thread_.join(); } @@ -62,12 +60,17 @@ class PeriodicTaskExecutor : public ForkableInterface { void prefork() final { shutdown(); - thread_.join(); + if (thread_.joinable()) { + thread_.join(); + } } void postfork([[maybe_unused]] bool child) final { working_ = true; + + mutex_.lock(); thread_ = std::thread(getThreadWorkerFunction()); + mutex_.unlock(); pauseCondition_.notify_all(); } @@ -107,8 +110,8 @@ class PeriodicTaskExecutor : public ForkableInterface { std::chrono::milliseconds sleepInterval_ = std::chrono::milliseconds(20); std::vector periodicTasks_; worker_init_t workerInit_; - std::thread thread_; std::mutex mutex_; + std::thread thread_; std::condition_variable pauseCondition_; bool working_ = true; bool resumed_ = false; diff --git a/agent/native/libcommon/code/PhpBridgeInterface.h b/agent/native/libcommon/code/PhpBridgeInterface.h index fc74e4404..e4ccaf93b 100644 --- a/agent/native/libcommon/code/PhpBridgeInterface.h +++ b/agent/native/libcommon/code/PhpBridgeInterface.h @@ -22,7 +22,7 @@ class PhpBridgeInterface { virtual std::string getPhpInfo() const = 0; virtual std::string_view getPhpSapiName() const = 0; - + virtual bool isExtensionLoaded(std::string_view extensionName) const = 0; }; } diff --git a/agent/native/libcommon/code/elastic_apm_version.h b/agent/native/libcommon/code/elastic_apm_version.h index 662d18a2b..d85bdb657 100644 --- a/agent/native/libcommon/code/elastic_apm_version.h +++ b/agent/native/libcommon/code/elastic_apm_version.h @@ -18,4 +18,4 @@ */ #pragma once -#define PHP_ELASTIC_APM_VERSION "1.13.0" +#define PHP_ELASTIC_APM_VERSION "1.15.0" diff --git a/agent/native/libcommon/test/PeriodicTaskExecutorTest.cpp b/agent/native/libcommon/test/PeriodicTaskExecutorTest.cpp index 0e77b9266..b196df541 100644 --- a/agent/native/libcommon/test/PeriodicTaskExecutorTest.cpp +++ b/agent/native/libcommon/test/PeriodicTaskExecutorTest.cpp @@ -47,7 +47,13 @@ TEST(PeriodicTaskExecutorTest, resumeAfterFork) { periodicTaskExecutor_.setInterval(20ms); - pthread_atfork(fh_prepare, fh_parent, fh_child); + + static bool pthread_atfork_called = false; + + if (!pthread_atfork_called) { + pthread_atfork(fh_prepare, fh_parent, fh_child); + pthread_atfork_called = true; + } periodicTaskExecutor_.resumePeriodicTasks(); std::this_thread::sleep_for(100ms); @@ -60,7 +66,7 @@ TEST(PeriodicTaskExecutorTest, resumeAfterFork) { ASSERT_GE(counter.load(), 13); // should be 15 in ideal world if (pid == 0) { - exit(0); + exit(testing::Test::HasFailure()); } } diff --git a/agent/native/libphpbridge/code/Debugging.cpp b/agent/native/libphpbridge/code/Debugging.cpp index e693aaf76..f52cbfc75 100644 --- a/agent/native/libphpbridge/code/Debugging.cpp +++ b/agent/native/libphpbridge/code/Debugging.cpp @@ -5,11 +5,15 @@ #include
#include #include +#include namespace elasticapm::php { using namespace std::string_view_literals; +bool PhpBridge::isExtensionLoaded(std::string_view extensionName) const { + return zend_hash_str_find(&module_registry, extensionName.data(), extensionName.length()) != nullptr; +} static int getModuleData(zval *item, void *arg) { zend_module_entry *module = (zend_module_entry *)Z_PTR_P(item); @@ -64,7 +68,7 @@ std::string PhpBridge::getPhpInfo() const { std::string output; phpInfoTempBuffer = &output; - + auto orig_php_info_as_text = sapi_module.phpinfo_as_text; sapi_module.phpinfo_as_text = 1; diff --git a/agent/native/libphpbridge/code/Exceptions.cpp b/agent/native/libphpbridge/code/Exceptions.cpp new file mode 100644 index 000000000..291a1baa1 --- /dev/null +++ b/agent/native/libphpbridge/code/Exceptions.cpp @@ -0,0 +1,56 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#include "Exceptions.h" +#include + +namespace elasticapm::php +{ + +SavedException saveExceptionState() +{ + SavedException savedException; + savedException.exception = EG( exception ); + savedException.prev_exception = EG( prev_exception ); + savedException.opline_before_exception = EG( opline_before_exception ); + + EG( exception ) = nullptr; + EG( prev_exception ) = nullptr; + EG( opline_before_exception ) = nullptr; + + if ( EG( current_execute_data ) ) + { + savedException.opline = EG( current_execute_data )->opline; + } + return savedException; +} + +void restoreExceptionState( SavedException savedException ) +{ + EG( exception ) = savedException.exception; + EG( prev_exception ) = savedException.prev_exception; + EG( opline_before_exception ) = savedException.opline_before_exception; + + if ( EG( current_execute_data ) && savedException.opline.has_value() ) + { + EG( current_execute_data )->opline = savedException.opline.value(); + } +} + +}// namespace elasticapm::php diff --git a/agent/native/libphpbridge/code/Exceptions.h b/agent/native/libphpbridge/code/Exceptions.h new file mode 100644 index 000000000..27908e7df --- /dev/null +++ b/agent/native/libphpbridge/code/Exceptions.h @@ -0,0 +1,61 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#pragma once + +#include +#include +#include +#include + +namespace elasticapm::php +{ + +struct SavedException { + zend_object* exception = nullptr; + zend_object* prev_exception = nullptr; + const zend_op* opline_before_exception = nullptr; + std::optional< const zend_op* > opline; +}; + +SavedException saveExceptionState(); +void restoreExceptionState( SavedException savedException ); + + +class AutomaticExceptionStateRestorer +{ +public: + AutomaticExceptionStateRestorer() + : savedException( saveExceptionState() ) + { + } + ~AutomaticExceptionStateRestorer() + { + restoreExceptionState( savedException ); + } + auto getException() + { + return savedException.exception; + } + +private: + SavedException savedException; +}; + +}// namespace elasticapm::php diff --git a/agent/native/libphpbridge/code/PhpBridge.h b/agent/native/libphpbridge/code/PhpBridge.h index 221d90cd4..f28e92070 100644 --- a/agent/native/libphpbridge/code/PhpBridge.h +++ b/agent/native/libphpbridge/code/PhpBridge.h @@ -17,6 +17,7 @@ class PhpBridge : public PhpBridgeInterface { std::string getPhpInfo() const final; std::string_view getPhpSapiName() const final; + bool isExtensionLoaded(std::string_view extensionName) const final; protected: zend_class_entry *findClassEntry(std::string_view className) const; diff --git a/agent/native/loader/code/phpdetection.cpp b/agent/native/loader/code/phpdetection.cpp index 5f618308d..42f58e653 100644 --- a/agent/native/loader/code/phpdetection.cpp +++ b/agent/native/loader/code/phpdetection.cpp @@ -56,9 +56,10 @@ bool isThreadSafe() { std::tuple getZendModuleApiVersion(std::string_view zendVersion) { using namespace std::string_view_literals; - constexpr size_t knownVersionsCount = 16; + constexpr size_t knownVersionsCount = 17; constexpr std::array, knownVersionsCount> knownPhpVersions {{ + {"4.4"sv, 20240924, true}, // PHP 8.4 {"4.3"sv, 20230831, true}, // PHP 8.3 {"4.2"sv, 20220829, true}, // PHP 8.2 {"4.1"sv, 20210902, true}, // PHP 8.1 @@ -91,4 +92,4 @@ std::tuple getZendModuleApiVersion(std::string_view -} \ No newline at end of file +} diff --git a/agent/php/ElasticApm/ElasticApm.php b/agent/php/ElasticApm/ElasticApm.php index b9eb08bdb..576ed59e8 100644 --- a/agent/php/ElasticApm/ElasticApm.php +++ b/agent/php/ElasticApm/ElasticApm.php @@ -35,7 +35,7 @@ final class ElasticApm { use StaticClassTrait; - public const VERSION = '1.13.0'; + public const VERSION = '1.15.0'; /** * Begins a new transaction and sets it as the current transaction. diff --git a/agent/php/ElasticApm/Impl/AutoInstrument/CurlHandleWrappedTrait.php b/agent/php/ElasticApm/Impl/AutoInstrument/CurlHandleWrappedTrait.php index 094c542bd..3080869f5 100644 --- a/agent/php/ElasticApm/Impl/AutoInstrument/CurlHandleWrappedTrait.php +++ b/agent/php/ElasticApm/Impl/AutoInstrument/CurlHandleWrappedTrait.php @@ -100,6 +100,7 @@ public function setOpt(int $option, $value): bool public function asInt(): int { + /** @phpstan-ignore-next-line */ return is_resource($this->curlHandle) ? intval($this->curlHandle) : spl_object_id($this->curlHandle); } diff --git a/agent/php/ElasticApm/Impl/AutoInstrument/TransactionForExtensionRequest.php b/agent/php/ElasticApm/Impl/AutoInstrument/TransactionForExtensionRequest.php index 95bd3376b..c3ab63af7 100644 --- a/agent/php/ElasticApm/Impl/AutoInstrument/TransactionForExtensionRequest.php +++ b/agent/php/ElasticApm/Impl/AutoInstrument/TransactionForExtensionRequest.php @@ -420,6 +420,11 @@ private static function isCliScript(): bool return PHP_SAPI === 'cli'; } + private static function sanitizeCliName(string $name): string + { + return preg_replace('/[^a-zA-Z0-9.:_\-]/', '_', $name) ?: ' '; + } + private function discoverCliName(): string { global $argc, $argv; @@ -441,7 +446,7 @@ private function discoverCliName(): string return self::DEFAULT_NAME; } - $cliScriptName = basename($argv[0]); + $cliScriptName = self::sanitizeCliName(basename($argv[0])); if ( ($argc < 2) || (count($argv) < 2) @@ -455,7 +460,7 @@ private function discoverCliName(): string return $cliScriptName; } - $txName = $cliScriptName . ' ' . $argv[1]; + $txName = $cliScriptName . ' ' . self::sanitizeCliName($argv[1]); ($loggerProxy = $this->logger->ifDebugLevelEnabled(__LINE__, __FUNCTION__)) && $loggerProxy->log( 'CLI script is Laravel ' . self::LARAVEL_ARTISAN_COMMAND_SCRIPT . ' command with arguments' @@ -567,6 +572,7 @@ private function discoverStartTime(float $requestInitStartTime): float return $requestInitStartTime; } + /** @phpstan-ignore-next-line */ $serverRequestTimeInSeconds = floatval($serverRequestTimeAsString); $serverRequestTimeInMicroseconds = $serverRequestTimeInSeconds * TimeUtil::NUMBER_OF_MICROSECONDS_IN_SECOND; if ($requestInitStartTime < $serverRequestTimeInMicroseconds) { diff --git a/agent/php/ElasticApm/Impl/Config/AllOptionsMetadata.php b/agent/php/ElasticApm/Impl/Config/AllOptionsMetadata.php index e3bd9ecd3..cf9dbbe01 100644 --- a/agent/php/ElasticApm/Impl/Config/AllOptionsMetadata.php +++ b/agent/php/ElasticApm/Impl/Config/AllOptionsMetadata.php @@ -82,7 +82,7 @@ public static function get(): array /** @var array> $value */ $value = [ OptionNames::API_KEY => new NullableStringOptionMetadata(), - OptionNames::AST_PROCESS_ENABLED => new BoolOptionMetadata(/* defaultValue: */ true), + OptionNames::AST_PROCESS_ENABLED => new BoolOptionMetadata(/* defaultValue: */ false), OptionNames::AST_PROCESS_DEBUG_DUMP_CONVERTED_BACK_TO_SOURCE => new BoolOptionMetadata(/* defaultValue: */ true), OptionNames::AST_PROCESS_DEBUG_DUMP_FOR_PATH_PREFIX => new NullableStringOptionMetadata(), diff --git a/agent/php/ElasticApm/Impl/Config/IniRawSnapshotSource.php b/agent/php/ElasticApm/Impl/Config/IniRawSnapshotSource.php index 56970a272..1ce0ebf40 100644 --- a/agent/php/ElasticApm/Impl/Config/IniRawSnapshotSource.php +++ b/agent/php/ElasticApm/Impl/Config/IniRawSnapshotSource.php @@ -79,6 +79,7 @@ private static function iniValueToString($iniValue): string return $iniValue ? 'true' : 'false'; } + /** @phpstan-ignore-next-line */ return strval($iniValue); } } diff --git a/agent/php/ElasticApm/Impl/InferredSpansManager.php b/agent/php/ElasticApm/Impl/InferredSpansManager.php index b8a316e04..433d9d086 100644 --- a/agent/php/ElasticApm/Impl/InferredSpansManager.php +++ b/agent/php/ElasticApm/Impl/InferredSpansManager.php @@ -205,11 +205,14 @@ private function onNewCurrentTransactionHasBegun(Transaction $transaction): void ($assertProxy = Assert::ifEnabled()) && $assertProxy->that($this->onCurrentSpanChangedCallback === null) && $assertProxy->withContext('$this->onCurrentSpanChangedCallback === null', ['this' => $this]); - $this->currentTransaction->onCurrentSpanChanged->add( - $this->onCurrentSpanChangedCallback = function (?Span $span): void { - $this->onCurrentSpanChanged($span); - } - ); + + if ($this->currentTransaction !== null) { + $this->currentTransaction->onCurrentSpanChanged->add( + $this->onCurrentSpanChangedCallback = function (?Span $span): void { + $this->onCurrentSpanChanged($span); + } + ); + } $this->builder = new InferredSpansBuilder($this->tracer); $this->state = self::STATE_RUNNING; diff --git a/agent/php/ElasticApm/Impl/Log/LoggableToJsonEncodable.php b/agent/php/ElasticApm/Impl/Log/LoggableToJsonEncodable.php index 895dd255c..923eaa29c 100644 --- a/agent/php/ElasticApm/Impl/Log/LoggableToJsonEncodable.php +++ b/agent/php/ElasticApm/Impl/Log/LoggableToJsonEncodable.php @@ -108,6 +108,7 @@ public static function convert($value, int $depth) return self::convertObject($value, $depth); } + /** @phpstan-ignore-next-line */ return [LogConsts::TYPE_KEY => DbgUtil::getType($value), LogConsts::VALUE_AS_STRING_KEY => strval($value)]; } diff --git a/agent/php/ElasticApm/Impl/Util/ArrayUtil.php b/agent/php/ElasticApm/Impl/Util/ArrayUtil.php index b79f7161d..881806d6c 100644 --- a/agent/php/ElasticApm/Impl/Util/ArrayUtil.php +++ b/agent/php/ElasticApm/Impl/Util/ArrayUtil.php @@ -33,17 +33,18 @@ final class ArrayUtil use StaticClassTrait; /** - * @param string $key - * @param array $array - * @param mixed $valueDst + * @template TKey of array-key + * @template TValue * - * @return bool + * @param TKey $key + * @param array $array + * @param TValue &$valueDst * - * @template T - * @phpstan-param T[] $array - * @phpstan-param T $valueDst + * @param-out TValue $valueDst + * + * @return bool */ - public static function getValueIfKeyExists(string $key, array $array, &$valueDst): bool + public static function getValueIfKeyExists($key, array $array, /* out */ &$valueDst): bool { if (!array_key_exists($key, $array)) { return false; @@ -54,7 +55,7 @@ public static function getValueIfKeyExists(string $key, array $array, &$valueDst } /** - * @template TKey of string|int + * @template TKey of array-key * @template TValue * * @param TKey $key diff --git a/agent/php/ElasticApm/Impl/Util/PhpErrorUtil.php b/agent/php/ElasticApm/Impl/Util/PhpErrorUtil.php index 3e6fdc727..9514823b5 100644 --- a/agent/php/ElasticApm/Impl/Util/PhpErrorUtil.php +++ b/agent/php/ElasticApm/Impl/Util/PhpErrorUtil.php @@ -32,41 +32,79 @@ final class PhpErrorUtil { use StaticClassTrait; - public static function getTypeName(int $type): ?string + /** + * @return array + */ + private static function flagsValueToNameMap(): array { - switch ($type) { - case E_ERROR: - return 'E_ERROR'; - case E_WARNING: - return 'E_WARNING'; - case E_PARSE: - return 'E_PARSE'; - case E_NOTICE: - return 'E_NOTICE'; - case E_CORE_ERROR: - return 'E_CORE_ERROR'; - case E_CORE_WARNING: - return 'E_CORE_WARNING'; - case E_COMPILE_ERROR: - return 'E_COMPILE_ERROR'; - case E_COMPILE_WARNING: - return 'E_COMPILE_WARNING'; - case E_USER_ERROR: - return 'E_USER_ERROR'; - case E_USER_WARNING: - return 'E_USER_WARNING'; - case E_USER_NOTICE: - return 'E_USER_NOTICE'; - case E_STRICT: - return 'E_STRICT'; - case E_RECOVERABLE_ERROR: - return 'E_RECOVERABLE_ERROR'; - case E_DEPRECATED: - return 'E_DEPRECATED'; - case E_USER_DEPRECATED: - return 'E_USER_DEPRECATED'; - default: - return null; + /** @var ?array $flags */ + static $flags = null; + + if ($flags === null) { + $flags = []; + $addToFlagsIfDefined = function (string $flagName, ?int $flagValue = null) use (&$flags): void { + if (defined($flagName)) { + $flagValueToUse = $flagValue ?? constant($flagName); + if (is_int($flagValueToUse)) { + $flags[$flagValueToUse] = $flagName; + } + } + }; + + $addToFlagsIfDefined('E_ERROR'); + $addToFlagsIfDefined('E_RECOVERABLE_ERROR'); + $addToFlagsIfDefined('E_WARNING'); + $addToFlagsIfDefined('E_PARSE'); + $addToFlagsIfDefined('E_NOTICE'); + // PHP 8.4: E_STRICT constant deprecated + if (PHP_VERSION_ID < 80400) { + $addToFlagsIfDefined('E_STRICT'); + } else { + $addToFlagsIfDefined('E_STRICT', /* E_STRICT: */ 2048); + } + $addToFlagsIfDefined('E_DEPRECATED'); + $addToFlagsIfDefined('E_CORE_ERROR'); + $addToFlagsIfDefined('E_CORE_WARNING'); + $addToFlagsIfDefined('E_COMPILE_ERROR'); + $addToFlagsIfDefined('E_COMPILE_WARNING'); + $addToFlagsIfDefined('E_USER_ERROR'); + $addToFlagsIfDefined('E_USER_WARNING'); + $addToFlagsIfDefined('E_USER_NOTICE'); + $addToFlagsIfDefined('E_USER_DEPRECATED'); + } + + return $flags; + } + + public static function convertErrorReportingValueToHumanReadableString(int $errorReporting): string + { + $flags = self::flagsValueToNameMap(); + $result = ''; + $appendToResult = function (string $separator, string $textToAppend) use (&$result): void { + if (!TextUtil::isEmptyString($result)) { + $result .= $separator; + } + $result .= $textToAppend; + }; + + $remaingValue = $errorReporting; + foreach ($flags as $flagValue => $flagName) { + $partToAddToResult = (($errorReporting & $flagValue) === 0 ? '~' : '') . $flagName; + $appendToResult(' & ', $partToAddToResult); + $remaingValue &= ~$flagValue; } + + if ($remaingValue !== 0) { + $appendToResult(' ', '[remaining value: ' . $remaingValue . ']'); + } + + $appendToResult(' ', '[value as int: ' . $errorReporting . ']'); + + return $result; + } + + public static function getTypeName(int $type): ?string + { + return ArrayUtil::getValueIfKeyExistsElse($type, self::flagsValueToNameMap(), null); } } diff --git a/composer.json b/composer.json index 8ca7db8c5..923abda65 100644 --- a/composer.json +++ b/composer.json @@ -8,7 +8,7 @@ { "name": "Sergey Kleyman" } ], "require": { - "php": "^7.2||8.0.*||8.1.*||8.2.*||8.3.*", + "php": "^7.2||8.0.*||8.1.*||8.2.*||8.3.*||8.4.*", "ext-json": "*", "ext-pcntl": "*", "psr/log": "^1.0" @@ -24,11 +24,11 @@ "guzzlehttp/guzzle": "^6.5.5||^7.4.4", "justinrainbow/json-schema": "^5.2.12", "monolog/monolog": "^2.7", - "php-ds/php-ds": "^1.4.1", + "php-ds/php-ds": "^1.5.0", "php-parallel-lint/php-console-highlighter": "^1.0", "php-parallel-lint/php-parallel-lint": "1.3.2", - "phpstan/phpstan": "1.10.14", - "phpstan/phpstan-phpunit": "^1.1.1", + "phpstan/phpstan": "^1.12", + "phpstan/phpstan-phpunit": "^1.4", "phpunit/phpunit": "^8.5||^9.5", "react/async": "^3.0", "react/http": "^1.6", diff --git a/demos/CakePHP/README.md b/demos/CakePHP/README.md index 6fac18e74..7096c36ae 100644 --- a/demos/CakePHP/README.md +++ b/demos/CakePHP/README.md @@ -3,8 +3,8 @@ Set up application. ``` -$ docker-compose build -$ docker-compose up +$ docker compose build +$ docker compose up ``` After it open the next link ``` diff --git a/demos/Codeigniter/README.md b/demos/Codeigniter/README.md index bc248127b..bcc8e696b 100644 --- a/demos/Codeigniter/README.md +++ b/demos/Codeigniter/README.md @@ -3,8 +3,8 @@ Setup application. ``` -$ docker-compose build -$ docker-compose up +$ docker compose build +$ docker compose up ``` Application url: diff --git a/demos/Laminas/README.md b/demos/Laminas/README.md index d93b7c307..433adfb27 100755 --- a/demos/Laminas/README.md +++ b/demos/Laminas/README.md @@ -3,8 +3,8 @@ Setup application with the next commands. ``` -$ docker-compose build -$ docker-compose up +$ docker compose build +$ docker compose up ``` Application url: diff --git a/demos/Laminas/public/index.php b/demos/Laminas/public/index.php index ab4122b7f..5a62399f1 100755 --- a/demos/Laminas/public/index.php +++ b/demos/Laminas/public/index.php @@ -34,7 +34,7 @@ "Unable to load application.\n" . "- Type `composer install` if you are developing locally.\n" . "- Type `vagrant ssh -c 'composer install'` if you are using Vagrant.\n" - . "- Type `docker-compose run laminas composer install` if you are using Docker.\n" + . "- Type `docker compose run laminas composer install` if you are using Docker.\n" ); } @@ -75,7 +75,7 @@ // "Unable to load application.\n" // . "- Type `composer install` if you are developing locally.\n" // . "- Type `vagrant ssh -c 'composer install'` if you are using Vagrant.\n" -// . "- Type `docker-compose run laminas composer install` if you are using Docker.\n" +// . "- Type `docker compose run laminas composer install` if you are using Docker.\n" // ); //} // diff --git a/demos/Magento/README-ELASTIC.md b/demos/Magento/README-ELASTIC.md index e81eaa0fc..18bc3ca26 100644 --- a/demos/Magento/README-ELASTIC.md +++ b/demos/Magento/README-ELASTIC.md @@ -1,7 +1,7 @@ # How to set up ? ``` -1. docker-compose build -2. docker-compose up +1. docker compose build +2. docker compose up ``` 1. Fronted: diff --git a/demos/Magento/README.md b/demos/Magento/README.md index 4d28e0354..37eabbd0a 100644 --- a/demos/Magento/README.md +++ b/demos/Magento/README.md @@ -12,7 +12,7 @@ Trademarks: This software listing is packaged by Bitnami. The respective tradema ```console $ curl -sSL https://raw.githubusercontent.com/bitnami/bitnami-docker-magento/master/docker-compose.yml > docker-compose.yml -$ docker-compose up -d +$ docker compose up -d ``` **Warning**: This quick setup is only intended for development environments. You are encouraged to change the insecure default credentials and check out the available configuration options in the [Environment Variables](#environment-variables) section for a more secure deployment. @@ -71,7 +71,7 @@ The main folder of this repository contains a functional [`docker-compose.yml`]( ```console $ curl -sSL https://raw.githubusercontent.com/bitnami/bitnami-docker-magento/master/docker-compose.yml > docker-compose.yml -$ docker-compose up -d +$ docker compose up -d ``` ### Using the Docker Command Line @@ -225,9 +225,9 @@ When the container is executed for the first time, it will execute the files wit ### Environment variables -When you start the Magento image, you can adjust the configuration of the instance by passing one or more environment variables either on the docker-compose file or on the `docker run` command line. If you want to add a new environment variable: +When you start the Magento image, you can adjust the configuration of the instance by passing one or more environment variables either on the docker compose file or on the `docker run` command line. If you want to add a new environment variable: - * For docker-compose add the variable name and value under the application section in the [`docker-compose.yml`](https://github.com/bitnami/bitnami-docker-magento/blob/master/docker-compose.yml) file present in this repository: + * For docker compose add the variable name and value under the application section in the [`docker-compose.yml`](https://github.com/bitnami/bitnami-docker-magento/blob/master/docker-compose.yml) file present in this repository: ```yaml magento: @@ -345,7 +345,7 @@ $ docker logs magento Or using Docker Compose: ```console -$ docker-compose logs magento +$ docker compose logs magento ``` You can configure the containers [logging driver](https://docs.docker.com/engine/admin/logging/overview/) using the `--log-driver` option if you wish to consume the container logs differently. In the default configuration docker uses the `json-file` driver. @@ -365,7 +365,7 @@ $ docker stop magento Or using Docker Compose: ```console -$ docker-compose stop magento +$ docker compose stop magento ``` #### Step 2: Run the backup command @@ -505,7 +505,7 @@ $ docker stop magento Or using Docker Compose: ```console -$ docker-compose stop magento +$ docker compose stop magento ``` #### Upgrading bundled image components @@ -523,7 +523,7 @@ $ docker pull bitnami/magento:latest Stop the currently running container using the command ```console -$ docker-compose stop magento +$ docker compose stop magento ``` ##### Step 3: Take a snapshot of the application state @@ -535,7 +535,7 @@ Follow the steps in [Backing up your container](#backing-up-your-container) to t Remove the currently running container by executing the following command: ```console -docker-compose rm -v magento +docker compose rm -v magento ``` ##### Step 5: Run the new image @@ -543,7 +543,7 @@ docker-compose rm -v magento Update the image tag in `docker-compose.yml` and re-create your container with the new image: ```console -$ docker-compose up -d +$ docker compose up -d ``` ## Customize this image diff --git a/demos/Shopware/README.md b/demos/Shopware/README.md index 298836607..6182d4f93 100644 --- a/demos/Shopware/README.md +++ b/demos/Shopware/README.md @@ -94,7 +94,7 @@ When you update the image version, automatically all required migrations will ru # Running multiple containers -See `docker-compose-advanced.yml` for a full docker-compose example. +See `docker-compose-advanced.yml` for a full docker compose example. ## Mode: default diff --git a/demos/Shopware/README_ELASTIC.md b/demos/Shopware/README_ELASTIC.md index ea2ae218e..967670bb3 100644 --- a/demos/Shopware/README_ELASTIC.md +++ b/demos/Shopware/README_ELASTIC.md @@ -1,7 +1,7 @@ # How to set up ? ``` -1. docker-compose build -2. docker-compose up +1. docker compose build +2. docker compose up ``` 1. Fronted: diff --git a/demos/Symfony/README.md b/demos/Symfony/README.md index 0617355d1..c4ce57efa 100644 --- a/demos/Symfony/README.md +++ b/demos/Symfony/README.md @@ -3,8 +3,8 @@ Set up application. ``` -$ docker-compose build -$ docker-compose up +$ docker compose build +$ docker compose up ``` After it open the next link ``` diff --git a/demos/WordPress/README.md b/demos/WordPress/README.md index 443667039..bf5faaf96 100644 --- a/demos/WordPress/README.md +++ b/demos/WordPress/README.md @@ -3,5 +3,5 @@ Setup application with a single command. ``` -$ docker-compose up +$ docker compose up ``` diff --git a/demos/Yii2/README.md b/demos/Yii2/README.md index b93a740e6..9e9d66802 100644 --- a/demos/Yii2/README.md +++ b/demos/Yii2/README.md @@ -86,15 +86,15 @@ http://localhost/basic/web/ Update your vendor packages - docker-compose run --rm php composer update --prefer-dist + docker compose run --rm php composer update --prefer-dist Run the installation triggers (creating cookie validation code) - docker-compose run --rm php composer install + docker compose run --rm php composer install Start the container - docker-compose up -d + docker compose up -d You can then access the application through the following URL: diff --git a/demos/Yii2/README_ELASTIC.md b/demos/Yii2/README_ELASTIC.md index 8a014326a..c3069bfb2 100644 --- a/demos/Yii2/README_ELASTIC.md +++ b/demos/Yii2/README_ELASTIC.md @@ -3,8 +3,8 @@ Set up application. ``` -$ docker-compose build -$ docker-compose up +$ docker compose build +$ docker compose up ``` After it open the next link ``` diff --git a/docs/release-notes.asciidoc b/docs/release-notes.asciidoc index 60ac753fa..f4c8de8a6 100644 --- a/docs/release-notes.asciidoc +++ b/docs/release-notes.asciidoc @@ -3,6 +3,11 @@ [[release-notes]] == Release notes +* <> +* <> +* <> +* <> +* <> * <> * <> * <> diff --git a/docs/setup.asciidoc b/docs/setup.asciidoc index f4f2b35dc..fbe5b315d 100644 --- a/docs/setup.asciidoc +++ b/docs/setup.asciidoc @@ -11,12 +11,14 @@ endif::[] === Prerequisites [discrete] -==== Operating system -The agent is currently only available for the Linux operating system. +==== Operating system and architecture +We officially support Linux systems (glibc, deb and rpm packages) and Alpine Linux (musl libc - apk packages) for x86_64 (AMD64) processors. + +NOTE: Experimentally, we also provide packages for the ARM64 architecture - please note that these packages have not been fully tested. [discrete] ==== PHP -The agent supports PHP versions 7.2-8.2. +The agent supports PHP versions 7.2-8.4. [discrete] ==== curl @@ -105,6 +107,21 @@ So if you would like to build `elastic_apm-*.so` on one machine and then deploy it on a different machine, you will need to copy both the built `elastic_apm-*.so` and the downloaded source files. -=== Limitations +[discrete] [[limitations]] -Please be aware that if the https://www.php.net/manual/en/ini.core.php#ini.open-basedir[open_basedir] option is configured in your php.ini, the installation directory of the agent (by default /opt/elastic/apm-agent-php) must be located within a path included in the https://www.php.net/manual/en/ini.core.php#ini.open-basedir[open_basedir] configuration. Otherwise, the agent will not be loaded correctly. +=== Limitations + +[discrete] +[[limitation-open_basedir]] +==== `open_basedir` PHP configuration option +Please be aware that if the https://www.php.net/manual/en/ini.core.php#ini.open-basedir[`open_basedir`] +option is configured in your php.ini, +the installation directory of the agent (by default `/opt/elastic/apm-agent-php`) +must be located within a path included in the +https://www.php.net/manual/en/ini.core.php#ini.open-basedir[`open_basedir`] value. +Otherwise, the agent will not be loaded correctly. + +[discrete] +[[limitation-xdebug]] +==== `Xdebug` stability and memory issues +We strongly advise against running the agent alongside the xdebug extension. Using both extensions simultaneously can lead to stability issues in the instrumented application, such as memory leaks. It is highly recommended to disable xdebug, preferably by preventing it from loading in the `php.ini` configuration file. \ No newline at end of file diff --git a/docs/supported-technologies.asciidoc b/docs/supported-technologies.asciidoc index 20a4c54a3..4537ef199 100644 --- a/docs/supported-technologies.asciidoc +++ b/docs/supported-technologies.asciidoc @@ -14,13 +14,14 @@ We will use the results to add support for the most requested technologies. [[supported-os]] === Operating systems -The agent supports Linux operating system. +We officially support Linux systems (glibc, deb and rpm packages) and Alpine Linux (musl libc - apk packages) for x86_64 (AMD64) processors. +Experimentally, we also provide packages for the ARM64 architecture - please note that these packages have not been fully tested. [float] [[supported-php-versions]] === PHP versions -The agent supports PHP versions 7.2-8.3. +The agent supports PHP versions 7.2-8.4. [float] [[unsupported-php-sapis]] diff --git a/docs/troubleshooting.asciidoc b/docs/troubleshooting.asciidoc index 21b99b3a4..3de042327 100644 --- a/docs/troubleshooting.asciidoc +++ b/docs/troubleshooting.asciidoc @@ -66,9 +66,10 @@ Also see <>. === Agent is not instrumenting code [source,bash] -==== open_basedir issue +==== `open_basedir` PHP configuration option -If you see a similar entry in the agent log, this indicates an incorrect open_basedir configuration. Please read the details in the <> +If you see a similar entry in the agent log, this indicates an incorrect open_basedir configuration. +For more details please see <>. ---- [Elastic APM PHP Tracer] 2023-08-23 14:38:12.223397+02:00 [PID: 268995] [TID: 268995] [WARNING] [Lifecycle] [lifecycle.cpp:558] [elasticApmModuleInit] Elastic Agent bootstrap file (/home/paplo/sources/apm-agent-php/agent/php/bootstrap_php_part.php) is located outside of paths allowed by open_basedir ini setting. Read more details here https://www.elastic.co/guide/en/apm/agent/php/current/setup.html ---- diff --git a/packaging/Dockerfile b/packaging/Dockerfile index 321171c43..df1d7f87c 100644 --- a/packaging/Dockerfile +++ b/packaging/Dockerfile @@ -1,18 +1,5 @@ -FROM ruby:2.7.1-alpine3.12 - -ENV FPM_VERSION 1.11.0 -RUN apk add --no-cache \ - alpine-sdk make cpio curl libarchive-tools make php-pear \ - python3 py3-virtualenv py3-setuptools py3-pip \ - rpm unzip xz git tar dpkg \ - && ln -sf python3 /usr/bin/python \ - && gem install --no-document fpm -v ${FPM_VERSION} - -## Fix fpm issue, see https://github.com/jordansissel/fpm/issues/1227 -ADD fpm_apm.patch /tmp -RUN (cd /usr/local/bundle/gems/fpm-${FPM_VERSION}/ ; patch -p 1 < /tmp/fpm_apm.patch ) \ - && rm -f /tmp/fpm_apk.patch - +ARG IMAGE_TAG=latest +FROM ghcr.io/elastic/apm-agent-php:${IMAGE_TAG} COPY create-package.sh /bin WORKDIR /app -ENTRYPOINT ["/bin/create-package.sh"] \ No newline at end of file +ENTRYPOINT ["/bin/create-package.sh"] diff --git a/packaging/Makefile b/packaging/Makefile index fa524660c..88b886324 100644 --- a/packaging/Makefile +++ b/packaging/Makefile @@ -1,6 +1,7 @@ SHELL = /bin/bash MAKEFLAGS += --no-print-directory IMAGE:=php-packaging +IMAGE_TAG:=latest NAME:=apm-agent-php VERSION?=$(shell grep 'VERSION' ../agent/php/ElasticApm/ElasticApm.php | cut -d= -f2 | tr -d " " | sed "s/'\(.*\)'.*/\1/g") OUTPUT:=build/packages @@ -10,6 +11,14 @@ SEL_DISTRO?=buster GIT_SHA?=$(shell git rev-parse HEAD || echo "unknown") RELEASE_VERSION?= GITHUB_RELEASES_URL=https://github.com/elastic/apm-agent-php/releases/download +BUILD_ARCH := $(if $(BUILD_ARCH),$(BUILD_ARCH),"x86-64") + +ifeq ($(shell [ $(PHP_VERSION) \< 8.4 ] && echo true), true) + RPM_IMAGE_VERSION = 0.0.3 # PHP < 8.4 - image using centos +else + RPM_IMAGE_VERSION = 0.1.1 # PHP 8.4+ - image using fedora +endif + export FPM_FLAGS= ifneq ($(PHP_VERSION), 7.2) @@ -27,7 +36,7 @@ clean: ## Clean the generated packages .PHONY: prepare prepare: ## Build docker image for the packaging - docker build -t $(IMAGE) . || exit 1 + docker build --build-arg IMAGE_TAG=$(IMAGE_TAG) -t $(IMAGE) . || exit 1 .PHONY: build-docker-images build-docker-images: prepare prepare-apk prepare-deb prepare-rpm prepare-tar prepare-deb-apache prepare-deb-fpm ## Build all the docker images @@ -45,7 +54,7 @@ create-%: prepare ## Create the specific package -e OUTPUT=$(OUTPUT) \ -e FPM_FLAGS=${FPM_FLAGS} \ -e PHP_AGENT_DIR=${PHP_AGENT_DIR} \ - -e BUILD_ARCH=x86-64 \ + -e BUILD_ARCH=$(BUILD_ARCH) \ -w /app $(IMAGE) @echo "::endgroup::" @@ -75,33 +84,57 @@ info: apk-info deb-info rpm-info tar-info ## Show the package metadata for all .PHONY: apk-info apk-info: ## Show the apk package metadata cd $(PWD) ;\ - BINARY=$$(ls -1 $(OUTPUT)/*.apk) ;\ + if [ "$(BUILD_ARCH)" = "arm64" ]; then \ + BINARY=$$(find $(OUTPUT)/ -type f -name "*aarch64*.apk" -o -name "*arm64*.apk" | head -n 1); \ + elif [ "$(BUILD_ARCH)" = "x86-64" ]; then \ + BINARY=$$(find $(OUTPUT)/ -type f -name "*x86_64*.apk" -o -name "*amd64*.apk" | head -n 1); \ + else \ + echo "Unsupported BUILD_ARCH: $(BUILD_ARCH)"; \ + exit 1; \ + fi; \ + echo $$BINARY; \ docker run --rm -v $(PWD):/app -w /app --entrypoint /sbin/apk $(IMAGE) manifest $$BINARY .PHONY: deb-info deb-info: ## Show the deb package metadata cd $(PWD) ;\ - BINARY=$$(ls -1 $(OUTPUT)/*.deb) ;\ + if [ "$(BUILD_ARCH)" = "arm64" ]; then \ + BINARY=$$(find $(OUTPUT)/ -type f -name "*aarch64*.deb" -o -name "*arm64*.deb" | head -n 1); \ + elif [ "$(BUILD_ARCH)" = "x86-64" ]; then \ + BINARY=$$(find $(OUTPUT)/ -type f -name "*x86_64*.deb" -o -name "*amd64*.deb" | head -n 1); \ + else \ + echo "Unsupported BUILD_ARCH: $(BUILD_ARCH)"; \ + exit 1; \ + fi; \ + echo $$BINARY; \ docker run --rm -v $(PWD):/app -w /app --entrypoint /usr/bin/dpkg $(IMAGE) --info $$BINARY ;\ docker run --rm -v $(PWD):/app -w /app --entrypoint /usr/bin/dpkg $(IMAGE) -c $$BINARY .PHONY: rpm-info rpm-info: ## Show the rpm package metadata cd $(PWD) ;\ - BINARY=$$(ls -1 $(OUTPUT)/*.rpm) ;\ + if [ "$(BUILD_ARCH)" = "arm64" ]; then \ + BINARY=$$(find $(OUTPUT)/ -type f -name "*aarch64*.rpm" -o -name "*arm64*.rpm" | head -n 1); \ + elif [ "$(BUILD_ARCH)" = "x86-64" ]; then \ + BINARY=$$(find $(OUTPUT)/ -type f -name "*x86_64*.rpm" -o -name "*amd64*.rpm" | head -n 1); \ + else \ + echo "Unsupported BUILD_ARCH: $(BUILD_ARCH)"; \ + exit 1; \ + fi; \ + echo $$BINARY; \ docker run --rm -v $(PWD):/app -w /app --entrypoint /usr/bin/rpm $(IMAGE) -qip $$BINARY ;\ docker run --rm -v $(PWD):/app -w /app --entrypoint /usr/bin/rpm $(IMAGE) -qlp $$BINARY .PHONY: tar-info tar-info: ## Show the tar package metadata cd $(PWD) ;\ - BINARY=$$(ls -1 $(OUTPUT)/$(NAME)-linux-x86-64*.tar) ;\ + BINARY=$$(ls -1 $(OUTPUT)/$(NAME)-linux-$(BUILD_ARCH)*.tar) ;\ docker run --rm -v $(PWD):/app -w /app --entrypoint /usr/bin/tar $(IMAGE) -tvf $$BINARY ;\ - BINARY=$$(ls -1 $(OUTPUT)/$(NAME)-debugsymbols-linux-x86-64.tar) ;\ + BINARY=$$(ls -1 $(OUTPUT)/$(NAME)-debugsymbols-linux-$(BUILD_ARCH).tar) ;\ docker run --rm -v $(PWD):/app -w /app --entrypoint /usr/bin/tar $(IMAGE) -tvf $$BINARY ;\ - BINARY=$$(ls -1 $(OUTPUT)/$(NAME)-linuxmusl-x86-64*.tar) ;\ + BINARY=$$(ls -1 $(OUTPUT)/$(NAME)-linuxmusl-$(BUILD_ARCH)*.tar) ;\ docker run --rm -v $(PWD):/app -w /app --entrypoint /usr/bin/tar $(IMAGE) -tvf $$BINARY ;\ - BINARY=$$(ls -1 $(OUTPUT)/$(NAME)-debugsymbols-linuxmusl-x86-64.tar) ;\ + BINARY=$$(ls -1 $(OUTPUT)/$(NAME)-debugsymbols-linuxmusl-$(BUILD_ARCH).tar) ;\ docker run --rm -v $(PWD):/app -w /app --entrypoint /usr/bin/tar $(IMAGE) -tvf $$BINARY .PHONY: prepare-apk @@ -172,7 +205,7 @@ tar-install: ## Install the tar installer to run some smoke tests .PHONY: rpm-install rpm-install: ## Install the rpm installer to run some smoke tests @echo "::group::$@" # Helping to group logs in GitHub actions - TYPE=rpm $(PWD)/.ci/run_docker_with_component_tests.sh elasticobservability/apm-agent-php-dev:packages-test-rpm-php-$(PHP_VERSION)-0.0.1 + TYPE=rpm $(PWD)/.ci/run_docker_with_component_tests.sh elasticobservability/apm-agent-php-dev:packages-test-rpm-php-$(PHP_VERSION)-$(RPM_IMAGE_VERSION) @echo "::endgroup::" .PHONY: deb-install-in-apache @@ -205,7 +238,7 @@ deb-install-release-github: ## Install the deb installer from a given release to .PHONY: rpm-install-release-github rpm-install-release-github: ## Install the rpm installer from a given release to run some smoke tests @echo "::group::$@" # Helping to group logs in GitHub actions - VERSION=$(RELEASE_VERSION) GITHUB_RELEASES_URL=$(GITHUB_RELEASES_URL) TYPE=release-github $(PWD)/.ci/run_docker_with_component_tests.sh elasticobservability/apm-agent-php-dev:packages-test-rpm-php-$(PHP_VERSION)-0.0.1 + VERSION=$(RELEASE_VERSION) GITHUB_RELEASES_URL=$(GITHUB_RELEASES_URL) TYPE=release-github $(PWD)/.ci/run_docker_with_component_tests.sh elasticobservability/apm-agent-php-dev:packages-test-rpm-php-$(PHP_VERSION)-$(RPM_IMAGE_VERSION) @echo "::endgroup::" .PHONY: tar-install-release-github @@ -247,7 +280,7 @@ deb-lifecycle-testing-in-fpm: ## Lifecycle testing for the deb installer with fp .PHONY: rpm-lifecycle-testing rpm-lifecycle-testing: ## Lifecycle testing for the rpm installer @echo "::group::$@" # Helping to group logs in GitHub actions - TYPE=rpm-uninstall PACKAGE=$(NAME) $(PWD)/.ci/run_docker_with_component_tests.sh elasticobservability/apm-agent-php-dev:packages-test-rpm-php-$(PHP_VERSION)-0.0.1 + TYPE=rpm-uninstall PACKAGE=$(NAME) $(PWD)/.ci/run_docker_with_component_tests.sh elasticobservability/apm-agent-php-dev:packages-test-rpm-php-$(PHP_VERSION)-$(RPM_IMAGE_VERSION) @echo "::endgroup::" .PHONY: tar-lifecycle-testing @@ -262,13 +295,13 @@ rpm-php-upgrade-testing: PHP_VERSION=7.2 rpm-php-upgrade-testing: ## PHP upgrade, from 7.2 to 7.4, testing for the rpm installer @echo "::group::$@" # Helping to group logs in GitHub actions echo "'$(PHP_VERSION)'" - TYPE=php-upgrade PHP_VERSION=$(PHP_VERSION) PACKAGE=$(NAME) $(PWD)/.ci/run_docker_with_component_tests.sh elasticobservability/apm-agent-php-dev:packages-test-rpm-php-$(PHP_VERSION)-0.0.2 + TYPE=php-upgrade PHP_VERSION=$(PHP_VERSION) PACKAGE=$(NAME) $(PWD)/.ci/run_docker_with_component_tests.sh elasticobservability/apm-agent-php-dev:packages-test-rpm-php-$(PHP_VERSION)-$(RPM_IMAGE_VERSION) @echo "::endgroup::" .PHONY: rpm-agent-upgrade-testing rpm-agent-upgrade-testing: ## Agent upgrade, from 1.0.0 to the current generated one, testing for the rpm installer @echo "::group::$@" # Helping to group logs in GitHub actions - VERSION=$(RELEASE_VERSION) GITHUB_RELEASES_URL=$(GITHUB_RELEASES_URL) TYPE=agent-upgrade PACKAGE=$(NAME) $(PWD)/.ci/run_docker_with_component_tests.sh elasticobservability/apm-agent-php-dev:packages-test-rpm-php-$(PHP_VERSION)-0.0.1 + VERSION=$(RELEASE_VERSION) GITHUB_RELEASES_URL=$(GITHUB_RELEASES_URL) TYPE=agent-upgrade PACKAGE=$(NAME) $(PWD)/.ci/run_docker_with_component_tests.sh elasticobservability/apm-agent-php-dev:packages-test-rpm-php-$(PHP_VERSION)-$(RPM_IMAGE_VERSION) @echo "::endgroup::" .PHONY: deb-agent-upgrade-testing @@ -280,7 +313,7 @@ deb-agent-upgrade-testing: ## Agent upgrade, from 1.0.0 to the current generated .PHONY: rpm-agent-upgrade-testing-local rpm-agent-upgrade-testing-local: ## Agent upgrade, from 1.0.0 to the current generated one, testing for the rpm installer @echo "::group::$@" # Helping to group logs in GitHub actions - VERSION=$(RELEASE_VERSION) GITHUB_RELEASES_URL=$(GITHUB_RELEASES_URL) TYPE=agent-upgrade-local PACKAGE=$(NAME) $(PWD)/.ci/run_docker_with_component_tests.sh elasticobservability/apm-agent-php-dev:packages-test-rpm-php-$(PHP_VERSION)-0.0.1 + VERSION=$(RELEASE_VERSION) GITHUB_RELEASES_URL=$(GITHUB_RELEASES_URL) TYPE=agent-upgrade-local PACKAGE=$(NAME) $(PWD)/.ci/run_docker_with_component_tests.sh elasticobservability/apm-agent-php-dev:packages-test-rpm-php-$(PHP_VERSION)-$(RPM_IMAGE_VERSION) @echo "::endgroup::" .PHONY: deb-agent-upgrade-testing-local diff --git a/packaging/cache/Dockerfile b/packaging/cache/Dockerfile new file mode 100644 index 000000000..e3f960a14 --- /dev/null +++ b/packaging/cache/Dockerfile @@ -0,0 +1,14 @@ +FROM ruby:3.3-alpine + +ENV FPM_VERSION 1.15.1 +RUN apk add --no-cache \ + alpine-sdk make cpio curl libarchive-tools make php-pear \ + python3 py3-virtualenv py3-setuptools py3-pip \ + rpm unzip xz git tar dpkg \ + && ln -sf python3 /usr/bin/python \ + && gem install --no-document fpm -v ${FPM_VERSION} + +## Fix fpm issue, see https://github.com/jordansissel/fpm/issues/1227 +ADD fpm_apm.patch /tmp +RUN (cd /usr/local/bundle/gems/fpm-${FPM_VERSION}/ ; patch -p 1 < /tmp/fpm_apm.patch ) \ + && rm -f /tmp/fpm_apk.patch diff --git a/packaging/fpm_apm.patch b/packaging/cache/fpm_apm.patch similarity index 61% rename from packaging/fpm_apm.patch rename to packaging/cache/fpm_apm.patch index fca506ac2..9f5f567c8 100644 --- a/packaging/fpm_apm.patch +++ b/packaging/cache/fpm_apm.patch @@ -1,25 +1,25 @@ -From b0c150228d9660d45144f470607bf796e36ba265 Mon Sep 17 00:00:00 2001 -From: Victor Martinez -Date: Thu, 23 Jul 2020 15:32:54 +0100 -Subject: [PATCH] As suggested in - https://github.com/jordansissel/fpm/issues/1227 +From 1715ee661dd284401f91b96a694e18f27742bce0 Mon Sep 17 00:00:00 2001 +From: Adrien Mannocci +Date: Tue, 26 Mar 2024 16:55:29 +0100 +Subject: [PATCH] fix: correct apk bad format +Signed-off-by: Adrien Mannocci --- lib/fpm/package/apk.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/fpm/package/apk.rb b/lib/fpm/package/apk.rb -index dd69067..9648e7f 100644 +index 596753c..eef881a 100644 --- a/lib/fpm/package/apk.rb +++ b/lib/fpm/package/apk.rb @@ -252,7 +252,7 @@ class FPM::Package::APK< FPM::Package - + # directories have a magic string inserted into their name full_record_path = extension_header[TAR_NAME_OFFSET_START..TAR_NAME_OFFSET_END].delete("\0") - full_record_path = add_paxstring(full_record_path) -+ # full_record_path = add_paxstring(full_record_path) - ++ #full_record_path = add_paxstring(full_record_path) + # hash data contents with sha1, if there is any content. if(typeflag == '5') --- -2.24.3 (Apple Git-128) \ No newline at end of file +-- +2.39.3 (Apple Git-146) diff --git a/packaging/create-package.sh b/packaging/create-package.sh index 9207a718e..01bb4d89b 100755 --- a/packaging/create-package.sh +++ b/packaging/create-package.sh @@ -117,25 +117,23 @@ rm "${OUTPUT}"/*.bck } - - # create second tar for musl if [ "${TYPE}" = 'tar' ] ; then NAME_BACKUP=${NAME} - NAME="${NAME_BACKUP}-linux-x86-64" - BUILD_EXT_DIR=agent/native/_build/linux-x86-64-release/ext/ - BUILD_LOADER_DIR=agent/native/_build/linux-x86-64-release/loader/code/ + NAME="${NAME_BACKUP}-linux-${BUILD_ARCH}" + BUILD_EXT_DIR=agent/native/_build/linux-${BUILD_ARCH}-release/ext/ + BUILD_LOADER_DIR=agent/native/_build/linux-${BUILD_ARCH}-release/loader/code/ createPackage - NAME="${NAME_BACKUP}-debugsymbols-linux-x86-64" + NAME="${NAME_BACKUP}-debugsymbols-linux-${BUILD_ARCH}" createDebugPackage - NAME="${NAME_BACKUP}-linuxmusl-x86-64" - BUILD_EXT_DIR=agent/native/_build/linuxmusl-x86-64-release/ext/ - BUILD_LOADER_DIR=agent/native/_build/linuxmusl-x86-64-release/loader/code/ + NAME="${NAME_BACKUP}-linuxmusl-${BUILD_ARCH}" + BUILD_EXT_DIR=agent/native/_build/linuxmusl-${BUILD_ARCH}-release/ext/ + BUILD_LOADER_DIR=agent/native/_build/linuxmusl-${BUILD_ARCH}-release/loader/code/ createPackage - NAME="${NAME_BACKUP}-debugsymbols-linuxmusl-x86-64" + NAME="${NAME_BACKUP}-debugsymbols-linuxmusl-${BUILD_ARCH}" createDebugPackage else createPackage diff --git a/packaging/post-install.sh b/packaging/post-install.sh index c40f5da2f..472af4852 100755 --- a/packaging/post-install.sh +++ b/packaging/post-install.sh @@ -205,11 +205,12 @@ function is_php_supported() { [ "${PHP_MAJOR_MINOR}" == "8.0" ] || \ [ "${PHP_MAJOR_MINOR}" == "8.1" ] || \ [ "${PHP_MAJOR_MINOR}" == "8.2" ] || \ - [ "${PHP_MAJOR_MINOR}" == "8.3" ] + [ "${PHP_MAJOR_MINOR}" == "8.3" ] || \ + [ "${PHP_MAJOR_MINOR}" == "8.4" ] then return 0 else - echo 'Failed. The supported PHP versions are 7.2-8.3.' + echo 'Failed. The supported PHP versions are 7.2-8.4.' return 1 fi } diff --git a/packaging/test/centos/CentOS-Base.repo b/packaging/test/centos/CentOS-Base.repo new file mode 100644 index 000000000..51f62fd46 --- /dev/null +++ b/packaging/test/centos/CentOS-Base.repo @@ -0,0 +1,31 @@ +[base] +name=CentOS-$releasever - Base +#mirrorlist=http://mirrorlist.centos.org/?release=$releasever&arch=$basearch&repo=os +baseurl=http://vault.centos.org/7.9.2009/os/$basearch/ +gpgcheck=1 +gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-7 + +#released updates +[updates] +name=CentOS-$releasever - Updates +#mirrorlist=http://mirrorlist.centos.org/?release=$releasever&arch=$basearch&repo=updates +baseurl=http://vault.centos.org/7.9.2009/updates/$basearch/ +gpgcheck=1 +gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-7 + +#additional packages that may be useful +[extras] +name=CentOS-$releasever - Extras +#mirrorlist=http://mirrorlist.centos.org/?release=$releasever&arch=$basearch&repo=extras +baseurl=http://vault.centos.org/7.9.2009/extras/$basearch/ +gpgcheck=1 +gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-7 + +#additional packages that extend functionality of existing packages +[centosplus] +name=CentOS-$releasever - Plus +#mirrorlist=http://mirrorlist.centos.org/?release=$releasever&arch=$basearch&repo=centosplus +baseurl=http://vault.centos.org/7.9.2009/centosplus/$basearch/ +gpgcheck=1 +enabled=0 +gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-7 diff --git a/packaging/test/centos/Dockerfile b/packaging/test/centos/Dockerfile index 2720cc9bd..12a203703 100644 --- a/packaging/test/centos/Dockerfile +++ b/packaging/test/centos/Dockerfile @@ -2,6 +2,8 @@ FROM centos:centos7 ARG PHP_VERSION=7.2 ENV PHP_VERSION ${PHP_VERSION} +COPY CentOS-Base.repo /etc/yum.repos.d/CentOS-Base.repo + RUN yum update -y \ && yum install -y \ git \ @@ -23,7 +25,6 @@ RUN export PHP_VERSION_TRANSFORMED=$(echo "${PHP_VERSION}" | sed 's#\.##g') \ && yum install -y \ php \ php-mbstring \ - php-mysql \ php-mysqli \ php-pcntl \ php-posix \ diff --git a/packaging/test/docker-compose.yml b/packaging/test/docker-compose.yml index b94d35435..85e9ff9ab 100644 --- a/packaging/test/docker-compose.yml +++ b/packaging/test/docker-compose.yml @@ -1,5 +1,13 @@ version: "3" services: + deb-fpm-php84: + image: elasticobservability/apm-agent-php-dev:packages-test-deb-fpm-php-8.4-0.0.1 + build: + context: ubuntu + dockerfile: fpm/Dockerfile + args: + - PHP_VERSION=8.4 + - SEL_DISTRO=bullseye deb-fpm-php83: image: elasticobservability/apm-agent-php-dev:packages-test-deb-fpm-php-8.3-0.0.1 build: @@ -54,6 +62,14 @@ services: + deb-apache-php84: + image: elasticobservability/apm-agent-php-dev:packages-test-deb-apache-php-8.4-0.0.1 + build: + context: ubuntu + dockerfile: apache/Dockerfile + args: + - PHP_VERSION=8.4 + - SEL_DISTRO=bullseye deb-apache-php83: image: elasticobservability/apm-agent-php-dev:packages-test-deb-apache-php-8.3-0.0.1 build: @@ -106,6 +122,14 @@ services: - PHP_VERSION=7.2 + deb-php84: + image: elasticobservability/apm-agent-php-dev:packages-test-deb-php-8.4-0.0.1 + build: + context: ubuntu + dockerfile: Dockerfile + args: + - PHP_VERSION=8.4 + - SEL_DISTRO=bullseye deb-php83: image: elasticobservability/apm-agent-php-dev:packages-test-deb-php-8.3-0.0.1 build: @@ -157,56 +181,70 @@ services: args: - PHP_VERSION=7.2 + rpm-php84: + image: elasticobservability/apm-agent-php-dev:packages-test-rpm-php-8.4-0.1.1 + build: + context: fedora + dockerfile: Dockerfile + args: + - PHP_VERSION=8.4 rpm-php83: - image: elasticobservability/apm-agent-php-dev:packages-test-rpm-php-8.3-0.0.1 + image: elasticobservability/apm-agent-php-dev:packages-test-rpm-php-8.3-0.0.3 build: context: centos dockerfile: Dockerfile args: - PHP_VERSION=8.3 rpm-php82: - image: elasticobservability/apm-agent-php-dev:packages-test-rpm-php-8.2-0.0.1 + image: elasticobservability/apm-agent-php-dev:packages-test-rpm-php-8.2-0.0.3 build: context: centos dockerfile: Dockerfile args: - PHP_VERSION=8.2 rpm-php81: - image: elasticobservability/apm-agent-php-dev:packages-test-rpm-php-8.1-0.0.1 + image: elasticobservability/apm-agent-php-dev:packages-test-rpm-php-8.1-0.0.3 build: context: centos dockerfile: Dockerfile args: - PHP_VERSION=8.1 rpm-php80: - image: elasticobservability/apm-agent-php-dev:packages-test-rpm-php-8.0-0.0.1 + image: elasticobservability/apm-agent-php-dev:packages-test-rpm-php-8.0-0.0.3 build: context: centos dockerfile: Dockerfile args: - PHP_VERSION=8.0 rpm-php74: - image: elasticobservability/apm-agent-php-dev:packages-test-rpm-php-7.4-0.0.1 + image: elasticobservability/apm-agent-php-dev:packages-test-rpm-php-7.4-0.0.3 build: context: centos dockerfile: Dockerfile args: - PHP_VERSION=7.4 rpm-php73: - image: elasticobservability/apm-agent-php-dev:packages-test-rpm-php-7.3-0.0.1 + image: elasticobservability/apm-agent-php-dev:packages-test-rpm-php-7.3-0.0.3 build: context: centos dockerfile: Dockerfile args: - PHP_VERSION=7.3 rpm-php72: - image: elasticobservability/apm-agent-php-dev:packages-test-rpm-php-7.2-0.0.2 + image: elasticobservability/apm-agent-php-dev:packages-test-rpm-php-7.2-0.0.3 build: context: centos dockerfile: Dockerfile args: - PHP_VERSION=7.2 + apk-php84: + image: elasticobservability/apm-agent-php-dev:packages-test-apk-php-8.4-0.0.1 + build: + context: alpine + dockerfile: Dockerfile + args: + - PHP_VERSION=8.4 apk-php83: image: elasticobservability/apm-agent-php-dev:packages-test-apk-php-8.3-0.0.1 build: diff --git a/packaging/test/fedora/Dockerfile b/packaging/test/fedora/Dockerfile new file mode 100644 index 000000000..de2f42b1a --- /dev/null +++ b/packaging/test/fedora/Dockerfile @@ -0,0 +1,61 @@ +FROM fedora:41 +ARG PHP_VERSION=8.4 +ENV PHP_VERSION ${PHP_VERSION} + +RUN echo "PHP_VERSION: $PHP_VERSION" +RUN echo "ls -R -l" && echo `ls -R -l` +RUN ls -1 /etc/*release | xargs -i sh -c 'echo {} && cat {}' + +RUN dnf -y update && dnf install -y \ + git \ + gnupg2 \ + logrotate \ + lsof \ + perl-Digest-SHA \ + rsyslog \ + unzip \ + wget \ + which \ + procps + + # && yum-config-manager --enable remi-php${PHP_VERSION_TRANSFORMED} \ +RUN export PHP_VERSION_TRANSFORMED=$(echo "${PHP_VERSION}" | sed 's#\.##g') \ + FEDORA_VERSION_ID=$(cat /etc/*release | grep VERSION_ID | cut -d '=' -f 2) && echo "FEDORA_VERSION_ID: $FEDORA_VERSION_ID" \ + && dnf -y install https://rpms.remirepo.net/fedora/remi-release-$FEDORA_VERSION_ID.rpm +RUN dnf -y install dnf-plugins-core + +RUN dnf -y module reset php \ + && dnf -y module enable php:remi-$PHP_VERSION -y + +# composer \ +RUN dnf -y install \ + php \ + php-cli \ + php-curl \ + php-json \ + php-mbstring \ + php-mysqli \ + php-pdo_sqlite \ + php-pcntl \ + php-posix \ + php-xml + + +COPY --from=composer:latest /usr/bin/composer /usr/bin/composer + +# Disable agent for auxiliary PHP processes to reduce noise in logs +ENV ELASTIC_APM_ENABLED=false + +# It seems that even though we install rsyslog and start it (search for ensureSyslogIsRunning in .ci/validate_agent_installation.sh) +# for some reason on CentOS rsyslog does not receive messages from the agent +# so in order to work around this issue we escalate all log levels on CentOS +ENV ELASTIC_APM_PHP_TESTS_ESCALATED_RERUNS_PROD_CODE_LOG_LEVEL_OPTION_NAME=log_level + +# To support tar and rpm packages +ENV TYPE=rpm +ENV VERSION= +ENV GITHUB_RELEASES_URL= +COPY entrypoint.sh /bin +WORKDIR /app + +ENTRYPOINT ["/bin/entrypoint.sh"] diff --git a/packaging/test/fedora/entrypoint.sh b/packaging/test/fedora/entrypoint.sh new file mode 100755 index 000000000..649798cd6 --- /dev/null +++ b/packaging/test/fedora/entrypoint.sh @@ -0,0 +1,135 @@ +#!/usr/bin/env bash +set -xe + +################### +#### VARIABLES #### +################### +BUILD_RELEASES_FOLDER=build/releases +BUILD_PACKAGES=build/packages + +################### +#### FUNCTIONS #### +################### +function download() { + package=$1 + folder=$2 + url=$3 + mkdir -p "${folder}" + wget -q https://artifacts.elastic.co/GPG-KEY-elasticsearch -O "${folder}/GPG-KEY-elasticsearch" + wget -q "${url}/${package}" -O "${folder}/${package}" + wget -q "${url}/${package}.sha512" -O "${folder}/${package}.sha512" + wget -q "${url}/${package}.asc" -O "${folder}/${package}.asc" + cd "${folder}" || exit + gpg --import "GPG-KEY-elasticsearch" + shasum -a 512 -c "${package}.sha512" + gpg --verify "${package}.asc" "${package}" + cd - +} + +function validate_if_agent_is_uninstalled() { + ## Validate if the elastic php agent has been uninstalled + php -m + if php -m | grep -q "Unable to load dynamic library '/opt/elastic/apm-agent-php/extensions" ; then + echo 'Extension has not been uninstalled.' + exit 1 + fi + if php -m | grep -q 'elastic' ; then + echo 'Extension has not been uninstalled.' + exit 1 + fi +} + +function validate_if_agent_is_enabled() { + ## Validate if the elastic php agent is enabled + if ! php -m | grep -q 'elastic' ; then + echo 'Extension has not been installed.' + exit 1 + fi +} + +function validate_agent_installation() { + .ci/validate_agent_installation.sh || exit $? +} + +############## +#### MAIN #### +############## +if [[ "${TYPE}" == "rpm" || "${TYPE}" == "rpm-uninstall" || "${TYPE}" == "php-upgrade" ]] ; then + ls -l $BUILD_PACKAGES + ## Install rpm package and configure the agent accordingly + rpm -ivh $BUILD_PACKAGES/*.rpm +elif [ "${TYPE}" == "release-github" ] ; then + ## fpm replaces - with _ in the version for rpms. + PACKAGE=apm-agent-php-${VERSION/-/_}-1.noarch.rpm + download "${PACKAGE}" "${BUILD_RELEASES_FOLDER}" "${GITHUB_RELEASES_URL}/v${VERSION}" + rpm -ivh "${BUILD_RELEASES_FOLDER}/${PACKAGE}" +elif [ "${TYPE}" == "release-tar-github" ] ; then + PACKAGE=apm-agent-php-linux-x86-64.tar + download "${PACKAGE}" "${BUILD_RELEASES_FOLDER}" "${GITHUB_RELEASES_URL}/v${VERSION}" + ## Install tar package and configure the agent accordingly + tar -xf ${BUILD_RELEASES_FOLDER}/${PACKAGE} -C / + # shellcheck disable=SC1091 + source /opt/elastic/apm-agent-php/bin/post-install.sh +elif [ "${TYPE}" == "agent-upgrade" ] ; then + ## fpm replaces - with _ in the version for rpms. + PACKAGE=apm-agent-php-${VERSION/-/_}-1.noarch.rpm + download "${PACKAGE}" "${BUILD_RELEASES_FOLDER}" "${GITHUB_RELEASES_URL}/v${VERSION}" + rpm -ivh "${BUILD_RELEASES_FOLDER}/${PACKAGE}" +elif [ "${TYPE}" == "agent-upgrade-local" ] ; then + rpm -ivh build/local/*.rpm +else + ## Install tar package and configure the agent accordingly + tar -xf $BUILD_PACKAGES/apm-agent-php-linux-x86-64.tar -C / + # shellcheck disable=SC1091 + source /opt/elastic/apm-agent-php/bin/post-install.sh +fi + +validate_if_agent_is_enabled + +if case $TYPE in agent-upgrade*) ;; *) false;; esac; then + echo 'Validate installation runs after the agent upgrade.' +else + validate_agent_installation +fi + +## Validate the uninstallation works as expected +set -ex +if [ "${TYPE}" == "rpm-uninstall" ] ; then + rpm -e "${PACKAGE}" + validate_if_agent_is_uninstalled +elif [ "${TYPE}" == "tar-uninstall" ] ; then + # shellcheck disable=SC1091 + source /opt/elastic/apm-agent-php/bin/before-uninstall.sh + validate_if_agent_is_uninstalled +elif [ "${TYPE}" == "php-upgrade" ] ; then + ## Copy existing configuration file to compare with + cp /opt/elastic/apm-agent-php/etc/elastic-apm.ini /tmp/elastic-apm-previous.ini + + ## Uninstall existing installation + rpm -e "${PACKAGE}" + + ## Upgrade PHP version + yum-config-manager --enable remi-php74 + yum install -y php php-mbstring php-mysql php-xml + + ## Install rpm package and configure the agent accordingly + rpm -ivh $BUILD_PACKAGES/*.rpm + if ! diff --report-identical-files /opt/elastic/apm-agent-php/etc/elastic-apm.ini /tmp/elastic-apm-previous.ini ; then + echo 'Configuration file has been modified but should be identical.' + exit 1 + fi + ## Validate agent is enabled + validate_if_agent_is_enabled + + ## Run some tests + validate_agent_installation +elif case $TYPE in agent-upgrade*) ;; *) false;; esac; then + ## Upgrade the agent version with the rpm package and configure the agent accordingly + rpm -Uvh build/packages/*.rpm + + ## Validate agent is enabled + validate_if_agent_is_enabled + + ## Run some tests + validate_agent_installation +fi diff --git a/tests/ElasticApmTests/ComponentTests/ConfigSettingTest.php b/tests/ElasticApmTests/ComponentTests/ConfigSettingTest.php index a7b553b45..a7ff0240e 100644 --- a/tests/ElasticApmTests/ComponentTests/ConfigSettingTest.php +++ b/tests/ElasticApmTests/ComponentTests/ConfigSettingTest.php @@ -228,6 +228,7 @@ public function dataProviderForTestAllWaysToSetConfig(): iterable $optRawVal = str_replace("\n", "\t", $optRawVal); } $optExpectedVal = $optExpectedVal ?? AllOptionsMetadata::get()[$optName]->defaultValue(); + /** @phpstan-ignore-next-line */ yield [$agentConfigSourceKind, $optName, strval($optRawVal), $optExpectedVal]; } } diff --git a/tests/ElasticApmTests/ComponentTests/CurlAutoInstrumentationTest.php b/tests/ElasticApmTests/ComponentTests/CurlAutoInstrumentationTest.php index be53d9855..b423d9c30 100644 --- a/tests/ElasticApmTests/ComponentTests/CurlAutoInstrumentationTest.php +++ b/tests/ElasticApmTests/ComponentTests/CurlAutoInstrumentationTest.php @@ -65,7 +65,7 @@ public static function appCodeClient(MixedMap $appCodeArgs): void $curlHandle = null; try { $curlHandle = HttpClientUtilForTests::createCurlHandleToSendRequestToAppCode( - (new UrlParts())->host(HttpServerHandle::DEFAULT_HOST)->port($serverPort), + (new UrlParts())->host(HttpServerHandle::CLIENT_LOCALHOST_ADDRESS)->port($serverPort), $dataPerRequest, self::buildResourcesClientForAppCode() ); diff --git a/tests/ElasticApmTests/ComponentTests/GenerateUnpackScriptsTest.php b/tests/ElasticApmTests/ComponentTests/GenerateUnpackScriptsTest.php index c5f82c498..60322db0d 100644 --- a/tests/ElasticApmTests/ComponentTests/GenerateUnpackScriptsTest.php +++ b/tests/ElasticApmTests/ComponentTests/GenerateUnpackScriptsTest.php @@ -52,7 +52,7 @@ final class GenerateUnpackScriptsTest extends ComponentTestCaseBase implements L private const PHP_VERSION_7_4 = '7.4'; // Make sure list of PHP versions supported by the Elastic APM PHP Agent is in sync. // See the comment in .ci/shared.sh - private const SUPPORTED_PHP_VERSIONS = ['7.2', '7.3', self::PHP_VERSION_7_4, '8.0', '8.1', '8.2', '8.3']; + private const SUPPORTED_PHP_VERSIONS = ['7.2', '7.3', self::PHP_VERSION_7_4, '8.0', '8.1', '8.2', '8.3', '8.4']; private const LINUX_PACKAGE_TYPE_DEB = 'deb'; private const LINUX_PACKAGE_TYPE_RPM = 'rpm'; @@ -393,8 +393,22 @@ private function assertAllTestsAreLeaf(array $whereEnvVars): void private function assertSufficientCoverageAgentUpgrade(): void { + /** @noinspection PhpForeachOverSingleElementArrayLiteralInspection */ + foreach ([self::PHP_VERSION_7_4] as $phpVersion) { + /** @noinspection PhpForeachOverSingleElementArrayLiteralInspection */ + foreach ([self::LINUX_PACKAGE_TYPE_RPM] as $linuxPackageType) { + $this->assertAllTestsAreSmoke( + [ + self::PHP_VERSION_KEY => $phpVersion, + self::LINUX_PACKAGE_TYPE_KEY => $linuxPackageType, + self::TESTING_TYPE_KEY => self::AGENT_UPGRADE_TESTING_TYPE, + ] + ); + } + } foreach ([self::PHP_VERSION_7_4, self::latestSupportedPhpVersion()] as $phpVersion) { - foreach ([self::LINUX_PACKAGE_TYPE_DEB, self::LINUX_PACKAGE_TYPE_RPM] as $linuxPackageType) { + /** @noinspection PhpForeachOverSingleElementArrayLiteralInspection */ + foreach ([self::LINUX_PACKAGE_TYPE_DEB] as $linuxPackageType) { $this->assertAllTestsAreSmoke( [ self::PHP_VERSION_KEY => $phpVersion, diff --git a/tests/ElasticApmTests/ComponentTests/HttpTransactionTest.php b/tests/ElasticApmTests/ComponentTests/HttpTransactionTest.php index 5ffdb35bb..20f142539 100644 --- a/tests/ElasticApmTests/ComponentTests/HttpTransactionTest.php +++ b/tests/ElasticApmTests/ComponentTests/HttpTransactionTest.php @@ -86,10 +86,10 @@ function (HttpAppCodeRequestParams $appCodeRequestParams) use ($httpMethod, &$ex self::assertNotNull($tx->context->request); self::assertSame($httpMethod, $tx->context->request->method); self::assertNotNull($tx->context->request->url); - self::assertSame(HttpServerHandle::DEFAULT_HOST, $tx->context->request->url->domain); + self::assertSame(HttpServerHandle::CLIENT_LOCALHOST_ADDRESS, $tx->context->request->url->domain); self::assertNotNull($expectedUrlParts->port); $expectedFullUrl - = $expectedUrlParts->scheme . '://' . HttpServerHandle::DEFAULT_HOST . ':' . $expectedUrlParts->port . '/'; + = $expectedUrlParts->scheme . '://' . HttpServerHandle::CLIENT_LOCALHOST_ADDRESS . ':' . $expectedUrlParts->port . '/'; self::assertSame($expectedFullUrl, $tx->context->request->url->full); self::assertSame($expectedFullUrl, $tx->context->request->url->original); self::assertSame($expectedUrlParts->scheme, $tx->context->request->url->protocol); diff --git a/tests/ElasticApmTests/ComponentTests/MySQLiAutoInstrumentationTest.php b/tests/ElasticApmTests/ComponentTests/MySQLiAutoInstrumentationTest.php index 14eb41dab..258ef816f 100644 --- a/tests/ElasticApmTests/ComponentTests/MySQLiAutoInstrumentationTest.php +++ b/tests/ElasticApmTests/ComponentTests/MySQLiAutoInstrumentationTest.php @@ -134,7 +134,10 @@ public function testPrerequisitesSatisfied(): void AmbientContextForTests::testConfig()->mysqlDb ); self::assertNotNull($mySQLi); - self::assertTrue($mySQLi->ping()); + // Method mysqli::ping() is deprecated since PHP 8.4 + if (PHP_VERSION_ID < 80400) { + self::assertTrue($mySQLi->ping()); + } } public function testIsAutoInstrumentationEnabled(): void @@ -351,7 +354,11 @@ public static function appCodeForTestAutoInstrumentation(MixedMap $appCodeArgs): $mySQLiApiFacade = new ApiFacade($isOOPApi); $mySQLi = $mySQLiApiFacade->connect($host, $port, $user, $password, $connectDbName); self::assertNotNull($mySQLi); - self::assertTrue($mySQLi->ping()); + + // Method mysqli::ping() is deprecated since PHP 8.4 + if (PHP_VERSION_ID < 80400) { + self::assertTrue($mySQLi->ping()); + } if ($connectDbName !== $workDbName) { self::assertTrue($mySQLi->query(self::CREATE_DATABASE_IF_NOT_EXISTS_SQL_PREFIX . $workDbName)); @@ -447,7 +454,11 @@ private function implTestAutoInstrumentation(MixedMap $testArgs): void $expectedSpans = []; if ($isInstrumentationEnabled) { $expectedSpans[] = $expectationsBuilder->fromNames('mysqli', '__construct', 'mysqli_connect'); - $expectedSpans[] = $expectationsBuilder->fromNames('mysqli', 'ping'); + + // Method mysqli::ping() is deprecated since PHP 8.4 + if (PHP_VERSION_ID < 80400) { + $expectedSpans[] = $expectationsBuilder->fromNames('mysqli', 'ping'); + } if ($connectDbName !== $workDbName) { $expectedSpans[] = $expectationsBuilder->fromStatement(self::CREATE_DATABASE_IF_NOT_EXISTS_SQL_PREFIX . $workDbName); diff --git a/tests/ElasticApmTests/ComponentTests/Util/BuiltinHttpServerAppCodeHostStarter.php b/tests/ElasticApmTests/ComponentTests/Util/BuiltinHttpServerAppCodeHostStarter.php index e327f9fdb..473080fae 100644 --- a/tests/ElasticApmTests/ComponentTests/Util/BuiltinHttpServerAppCodeHostStarter.php +++ b/tests/ElasticApmTests/ComponentTests/Util/BuiltinHttpServerAppCodeHostStarter.php @@ -74,7 +74,7 @@ protected function buildCommandLine(array $ports): string { Assert::assertCount(1, $ports); return InfraUtilForTests::buildAppCodePhpCmd($this->agentConfigSourceBuilder->getPhpIniFile()) - . " -S localhost:" . $ports[0] + . ' -S ' . HttpServerHandle::SERVER_LOCALHOST_ADDRESS . ':' . $ports[0] . ' "' . FileUtilForTests::listToPath([__DIR__, self::APP_CODE_HOST_ROUTER_SCRIPT]) . '"'; } diff --git a/tests/ElasticApmTests/ComponentTests/Util/ComponentTestCaseBase.php b/tests/ElasticApmTests/ComponentTests/Util/ComponentTestCaseBase.php index c39134f6e..25859b172 100644 --- a/tests/ElasticApmTests/ComponentTests/Util/ComponentTestCaseBase.php +++ b/tests/ElasticApmTests/ComponentTests/Util/ComponentTestCaseBase.php @@ -24,6 +24,7 @@ namespace ElasticApmTests\ComponentTests\Util; use Elastic\Apm\Impl\AutoInstrument\AutoInstrumentationBase; +use Elastic\Apm\Impl\Config\AllOptionsMetadata; use Elastic\Apm\Impl\Config\OptionNames; use Elastic\Apm\Impl\GlobalTracerHolder; use Elastic\Apm\Impl\Log\Level as LogLevel; @@ -197,7 +198,9 @@ protected static function implTestIsAutoInstrumentationEnabled(string $instrClas $actualNames = $instr->keywords(); $actualNames[] = $instr->name(); self::assertEqualAsSets($expectedNames, $actualNames); - self::assertTrue($instr->isEnabled()); + $astProcessEnabledDefaultValue = AllOptionsMetadata::get()[OptionNames::AST_PROCESS_ENABLED]->defaultValue(); + $isEnabledByDefault = $astProcessEnabledDefaultValue || (!$instr->requiresUserlandCodeInstrumentation()); + self::assertSame($isEnabledByDefault, $instr->isEnabled()); /** * @param string $name @@ -238,17 +241,17 @@ protected static function implTestIsAutoInstrumentationEnabled(string $instrClas $dbgCtx->clearCurrentSubScope(['disableInstrumentationsOptVal' => $disableInstrumentationsOptVal]); $tracer = self::buildTracerForTests()->withConfig(OptionNames::DISABLE_INSTRUMENTATIONS, $disableInstrumentationsOptVal)->build(); $instr = new $instrClassName($tracer); - self::assertTrue($instr->isEnabled()); + self::assertSame($isEnabledByDefault, $instr->isEnabled()); } $dbgCtx->popSubScope(); $dbgCtx->pushSubScope(); foreach ([true, false] as $astProcessEnabled) { $dbgCtx->clearCurrentSubScope(['astProcessEnabled' => $astProcessEnabled]); - $expectedIsEnabled = $astProcessEnabled || (!$instr->requiresUserlandCodeInstrumentation()); + $isEnabledByDefault = $astProcessEnabled || (!$instr->requiresUserlandCodeInstrumentation()); $tracer = self::buildTracerForTests()->withConfig(OptionNames::AST_PROCESS_ENABLED, BoolUtil::toString($astProcessEnabled))->build(); $instr = new $instrClassName($tracer); - self::assertSame($expectedIsEnabled, $instr->isEnabled()); + self::assertSame($isEnabledByDefault, $instr->isEnabled()); } $dbgCtx->popSubScope(); } diff --git a/tests/ElasticApmTests/ComponentTests/Util/HttpAppCodeRequestParams.php b/tests/ElasticApmTests/ComponentTests/Util/HttpAppCodeRequestParams.php index b0a6aeafb..65608c4c2 100644 --- a/tests/ElasticApmTests/ComponentTests/Util/HttpAppCodeRequestParams.php +++ b/tests/ElasticApmTests/ComponentTests/Util/HttpAppCodeRequestParams.php @@ -42,7 +42,7 @@ public function __construct(HttpServerHandle $httpServerHandle, AppCodeTarget $a $this->urlParts = new UrlParts(); $this->urlParts->scheme('http') - ->host(HttpServerHandle::DEFAULT_HOST) + ->host(HttpServerHandle::CLIENT_LOCALHOST_ADDRESS) ->port($httpServerHandle->getMainPort()) ->path('/'); } diff --git a/tests/ElasticApmTests/ComponentTests/Util/HttpServerHandle.php b/tests/ElasticApmTests/ComponentTests/Util/HttpServerHandle.php index 9d408151f..692146a27 100644 --- a/tests/ElasticApmTests/ComponentTests/Util/HttpServerHandle.php +++ b/tests/ElasticApmTests/ComponentTests/Util/HttpServerHandle.php @@ -38,7 +38,8 @@ class HttpServerHandle implements LoggableInterface { use LoggableTrait; - public const DEFAULT_HOST = '127.0.0.1'; + public const CLIENT_LOCALHOST_ADDRESS = '127.0.0.1'; + public const SERVER_LOCALHOST_ADDRESS = self::CLIENT_LOCALHOST_ADDRESS; public const STATUS_CHECK_URI_PATH = '/elastic_apm_php_tests_status_check'; public const PID_KEY = 'pid'; diff --git a/tests/ElasticApmTests/ComponentTests/Util/HttpServerStarter.php b/tests/ElasticApmTests/ComponentTests/Util/HttpServerStarter.php index 162c21c9a..603a62dbb 100644 --- a/tests/ElasticApmTests/ComponentTests/Util/HttpServerStarter.php +++ b/tests/ElasticApmTests/ComponentTests/Util/HttpServerStarter.php @@ -223,7 +223,7 @@ function () use ($port, $dataPerRequest, $logger, &$lastThrown, &$pid) { try { $response = HttpClientUtilForTests::sendRequest( HttpConstantsForTests::METHOD_GET, - (new UrlParts())->host(HttpServerHandle::DEFAULT_HOST) + (new UrlParts())->host(HttpServerHandle::CLIENT_LOCALHOST_ADDRESS) ->port($port) ->path(HttpServerHandle::STATUS_CHECK_URI_PATH), $dataPerRequest diff --git a/tests/ElasticApmTests/ComponentTests/Util/SpawnedProcessBase.php b/tests/ElasticApmTests/ComponentTests/Util/SpawnedProcessBase.php index fabebbc75..9a7f23f83 100644 --- a/tests/ElasticApmTests/ComponentTests/Util/SpawnedProcessBase.php +++ b/tests/ElasticApmTests/ComponentTests/Util/SpawnedProcessBase.php @@ -35,6 +35,7 @@ use Elastic\Apm\Impl\Util\BoolUtil; use Elastic\Apm\Impl\Util\ClassNameUtil; use Elastic\Apm\Impl\Util\ExceptionUtil; +use Elastic\Apm\Impl\Util\PhpErrorUtil; use Elastic\Apm\Impl\Util\UrlParts; use ElasticApmTests\Util\LogCategoryForTests; use PHPUnit\Framework\TestCase; @@ -61,6 +62,7 @@ protected function __construct() [ 'AmbientContext::testConfig()' => AmbientContextForTests::testConfig(), 'Environment variables' => EnvVarUtilForTests::getAll(), + 'error_reporting()' => PhpErrorUtil::convertErrorReportingValueToHumanReadableString(error_reporting()), ] ); } diff --git a/tests/ElasticApmTests/ComponentTests/Util/TestCaseHandle.php b/tests/ElasticApmTests/ComponentTests/Util/TestCaseHandle.php index 5f6ea5c75..30135037f 100644 --- a/tests/ElasticApmTests/ComponentTests/Util/TestCaseHandle.php +++ b/tests/ElasticApmTests/ComponentTests/Util/TestCaseHandle.php @@ -214,7 +214,8 @@ private function setMandatoryOptions(AppCodeHostParams $params): void $escalatedLogLevelForProdCodeAsString = LogLevel::intToName($this->escalatedLogLevelForProdCode); $params->setAgentOption(AmbientContextForTests::testConfig()->escalatedRerunsProdCodeLogLevelOptionName ?? OptionNames::LOG_LEVEL_SYSLOG, $escalatedLogLevelForProdCodeAsString); } - $params->setAgentOption(OptionNames::SERVER_URL, 'http://localhost:' . $this->mockApmServer->getPortForAgent()); + /** @noinspection HttpUrlsUsage */ + $params->setAgentOption(OptionNames::SERVER_URL, 'http://' . HttpServerHandle::CLIENT_LOCALHOST_ADDRESS . ':' . $this->mockApmServer->getPortForAgent()); if (!$this->isTestSpanCompressionCompatible) { $params->setAgentOption(OptionNames::SPAN_COMPRESSION_ENABLED, false); diff --git a/tests/ElasticApmTests/ComponentTests/Util/TestInfraHttpServerProcessBase.php b/tests/ElasticApmTests/ComponentTests/Util/TestInfraHttpServerProcessBase.php index fa8073f16..38cfeb162 100644 --- a/tests/ElasticApmTests/ComponentTests/Util/TestInfraHttpServerProcessBase.php +++ b/tests/ElasticApmTests/ComponentTests/Util/TestInfraHttpServerProcessBase.php @@ -29,7 +29,9 @@ use Elastic\Apm\Impl\Log\Logger; use Elastic\Apm\Impl\Util\ArrayUtil; use Elastic\Apm\Impl\Util\ExceptionUtil; +use Elastic\Apm\Impl\Util\PhpErrorUtil; use ElasticApmTests\Util\LogCategoryForTests; +use ElasticApmTests\Util\TestCaseBase; use ErrorException; use Exception; use PHPUnit\Framework\Assert; @@ -72,23 +74,23 @@ public function __construct() __FILE__ )->addContext('this', $this); - set_error_handler( - function ( - int $type, - string $message, - string $srcFile, - int $srcLine - ): bool { - $msgForEx = LoggableToString::convert( - [ - 'message' => $message, - 'error type' => $type, - 'srcFile:srcLine' => $srcFile . ':' . $srcLine, - ] - ); + $prevHandler = set_error_handler( + function (int $type, string $message, string $srcFile, int $srcLine, ?array $context = null): bool { + $msgCtx = [ + 'message' => $message, + 'error type' => PhpErrorUtil::getTypeName($type) . ' (as int :' . $type . ')', + 'srcFile:srcLine' => $srcFile . ':' . $srcLine, + ]; + if ($context !== null) { + $msgCtx['context'] = $context; + } + + $msgForEx = LoggableToString::convert($msgCtx); throw new ErrorException($msgForEx, /* code: */ 0, $type, $srcFile, $srcLine); - } + }, + E_ALL & ~E_DEPRECATED ); + TestCaseBase::assertNull($prevHandler); } /** @inheritDoc */ @@ -167,7 +169,7 @@ private function runHttpServer(): void $this->reactLoop = Loop::get(); TestCase::assertNotEmpty(AmbientContextForTests::testConfig()->dataPerProcess->thisServerPorts); foreach (AmbientContextForTests::testConfig()->dataPerProcess->thisServerPorts as $port) { - $uri = HttpServerHandle::DEFAULT_HOST . ':' . $port; + $uri = HttpServerHandle::SERVER_LOCALHOST_ADDRESS . ':' . $port; $serverSocket = new SocketServer($uri, /* context */ [], $this->reactLoop); $socketIndex = count($this->serverSockets); $this->serverSockets[] = $serverSocket; diff --git a/tests/ElasticApmTests/ComponentTests/WordPressAutoInstrumentationTest.php b/tests/ElasticApmTests/ComponentTests/WordPressAutoInstrumentationTest.php index 35052cdfa..d41099400 100644 --- a/tests/ElasticApmTests/ComponentTests/WordPressAutoInstrumentationTest.php +++ b/tests/ElasticApmTests/ComponentTests/WordPressAutoInstrumentationTest.php @@ -41,15 +41,18 @@ use ElasticApmTests\ComponentTests\Util\ExpectedEventCounts; use ElasticApmTests\ComponentTests\WordPress\WordPressSpanExpectationsBuilder; use ElasticApmTests\ComponentTests\WordPress\WordPressMockBridge; +use ElasticApmTests\Util\ArrayUtilForTests; use ElasticApmTests\Util\AssertMessageStack; use ElasticApmTests\Util\DataProviderForTestBuilder; use ElasticApmTests\Util\FileUtilForTests; use ElasticApmTests\Util\LogCategoryForTests; +use ElasticApmTests\Util\LogSinkForTests; use ElasticApmTests\Util\MetadataExpectations; use ElasticApmTests\Util\MixedMap; use ElasticApmTests\Util\SpanExpectations; use ElasticApmTests\Util\SpanSequenceValidator; use ElasticApmTests\Util\StackTraceExpectations; +use ElasticApmTests\Util\TestCaseBase; use ElasticApmTests\Util\TextUtilForTests; use SplFileInfo; @@ -133,6 +136,10 @@ private static function getLoggerForThisClass(): Logger public function testIsAutoInstrumentationEnabled(): void { + if (self::skipNotStableEnv(__FUNCTION__)) { + return; + } + // In production code ELASTIC_APM_WORDPRESS_DIRECT_CALL_METHOD_SET_READY_TO_WRAP_FILTER_CALLBACKS is defined by the native part of the agent // but if we don't load elastic_apm extension in the component tests so we need to define a dummy $constantName = 'ELASTIC_APM_WORDPRESS_DIRECT_CALL_METHOD_SET_READY_TO_WRAP_FILTER_CALLBACKS'; @@ -213,6 +220,7 @@ public static function foldTextWithMarkersIntoOneLine(string $fileContents): str $adaptedLines[] = $line . $endOfLine; } + $dbgCtx->pop(); return implode(/* separator */ '', $adaptedLines); } @@ -276,6 +284,8 @@ private static function adaptSourceTree(bool $isExpectedVariant, string $fromDir $loggerProxyDebug && $loggerProxyDebug->log(__LINE__, 'Created file', ['adaptedSrcFileFullPath' => $adaptedSrcFileFullPath]); } $dbgCtx->popSubScope(); + + $dbgCtx->pop(); } /** @@ -296,6 +306,10 @@ public function dataProviderForTestAstProcessOnMockSource(): iterable */ public function testAstProcessOnMockSource(MixedMap $testArgs): void { + if (self::skipNotStableEnv(__FUNCTION__)) { + return; + } + $subDirName = FileUtilForTests::buildTempSubDirName(__CLASS__, __FUNCTION__); self::runAndEscalateLogLevelOnFailure( self::buildDbgDescForTestWithArtgs(__CLASS__, __FUNCTION__, $testArgs), @@ -334,6 +348,8 @@ private static function adaptManuallyInstrumentedGeneratedFile(/* in,out */ stri self::assertNotFalse(file_put_contents($adaptedFilePath, $adaptedFileContents)); $filePath = $adaptedFilePath; $fileContents = $adaptedFileContents; + + $dbgCtx->pop(); } private static function logFileContentOnMismatch(string $filePath, string $fileContents): void @@ -350,8 +366,7 @@ private static function logFileContentOnMismatch(string $filePath, string $fileC private static function verifyAstProcessGeneratedFiles(string $astProcessDebugDumpOutDir, string $phpFileRelativePath): void { - AssertMessageStack::newScope(/* out */ $dbgCtx); - $dbgCtx->add(['astProcessDebugDumpOutDir' => $astProcessDebugDumpOutDir, 'phpFileRelativePath' => $phpFileRelativePath]); + AssertMessageStack::newScope(/* out */ $dbgCtx, AssertMessageStack::funcArgs()); $logger = self::getLoggerForThisClass()->addAllContext(['astProcessDebugDumpOutDir' => $astProcessDebugDumpOutDir, 'phpFileRelativePath' => $phpFileRelativePath]); @@ -369,7 +384,7 @@ private static function verifyAstProcessGeneratedFiles(string $astProcessDebugDu $phpFileRelativePath ): void { AssertMessageStack::newScope(/* out */ $dbgCtx); - $dbgCtx->add(['isExpectedVariant' => $isExpectedVariant, 'isAstDebugDump' => $isAstDebugDump, 'fileFullPath' => $fileFullPath]); + $dbgCtx->add(compact('isExpectedVariant', 'isAstDebugDump', 'fileFullPath')); $outSubDir = self::buildInputOrExpectedOutputVariantSubDir($astProcessDebugDumpOutDir, $isExpectedVariant); $fileName = $phpFileRelativePath . '.' . ($isExpectedVariant ? self::BEFORE_AST_PROCESS_FILE_NAME_SUFFIX : self::AFTER_AST_PROCESS_FILE_NAME_SUFFIX); @@ -379,6 +394,8 @@ private static function verifyAstProcessGeneratedFiles(string $astProcessDebugDu self::assertFileExists($fileFullPath); $fileContents = file_get_contents($fileFullPath); self::assertNotFalse($fileContents); + + $dbgCtx->pop(); }; ($loggerProxy = $logger->ifDebugLevelEnabled(__LINE__, __FUNCTION__)) && $loggerProxy->log('Starting...'); @@ -405,9 +422,11 @@ private static function verifyAstProcessGeneratedFiles(string $astProcessDebugDu return; } - $logCtx = ['astMatches' => $astMatches]; + $logCtx = compact('astMatches'); + $dbgCtx->add(compact('astMatches', 'actualAstFilePath', 'expectedAstFilePath')); if (AmbientContextForTests::testConfig()->compareAstConvertedBackToSource) { - $logCtx['phpMatches'] = $phpMatches; + ArrayUtilForTests::append(compact('phpMatches'), /* in,out */ $logCtx); + $dbgCtx->add(compact('phpMatches', 'actualPhpFilePath', 'expectedPhpFilePath')); } ($loggerProxy = $logger->ifCriticalLevelEnabled(__LINE__, __FUNCTION__)) @@ -423,9 +442,9 @@ private static function verifyAstProcessGeneratedFiles(string $astProcessDebugDu } if (!$astMatches) { - self::assertSame($expectedAstFilePath, $actualAstFilePath); + TestCaseBase::fail('Dumpted ASTs do not match'); } elseif (AmbientContextForTests::testConfig()->compareAstConvertedBackToSource) { - self::assertSame($expectedPhpFilePath, $actualPhpFileContents); + TestCaseBase::fail('ASTs converted back to source do not match'); } } @@ -514,6 +533,8 @@ public static function appCodeForTestOnMockSource(MixedMap $appCodeArgs): void WordPressMockBridge::loadMockSource($srcVariantBaseDir, /* isExpectedVariant */ false); WordPressMockBridge::runMockSource($appCodeArgs); + + $dbgCtx->pop(); } public static function isWordPressDataToBeExpected(MixedMap $testArgs): bool @@ -646,6 +667,10 @@ function (AppCodeRequestParams $appCodeRequestParams) use ($testArgs): void { */ public function testOnMockSource(MixedMap $testArgs): void { + if (self::skipNotStableEnv(__FUNCTION__)) { + return; + } + self::runAndEscalateLogLevelOnFailure( self::buildDbgDescForTestWithArtgs(__CLASS__, __FUNCTION__, $testArgs), function () use ($testArgs): void { @@ -724,6 +749,8 @@ function (AppCodeRequestParams $appCodeRequestParams) use ($testArgs): void { self::assertSame($expectedServiceFramework->version, $metadata->service->framework->version); } } + + $dbgCtx->pop(); } /** @@ -731,6 +758,10 @@ function (AppCodeRequestParams $appCodeRequestParams) use ($testArgs): void { */ public function testFrameworkDiscovery(MixedMap $testArgs): void { + if (self::skipNotStableEnv(__FUNCTION__)) { + return; + } + self::runAndEscalateLogLevelOnFailure( self::buildDbgDescForTestWithArtgs(__CLASS__, __FUNCTION__, $testArgs), function () use ($testArgs): void { @@ -738,4 +769,24 @@ function () use ($testArgs): void { } ); } + + private static function skipNotStableEnv(string $testFunc): bool + { + $componentTestingMatrixRow = getenv('ELASTIC_APM_PHP_TESTS_MATRIX_ROW'); + if (!is_string($componentTestingMatrixRow)) { + return false; + } + + // Testing WordPress instrumentation based on AST processing is not stable on CentOS with PHP 8.0 and 8.3 + if (TextUtil::isPrefixOf('8.0,rpm,', $componentTestingMatrixRow) || TextUtil::isPrefixOf('8.3,rpm,', $componentTestingMatrixRow)) { + LogSinkForTests::writeLineToStdErr( + __CLASS__ . '::' . $testFunc . ' is effectively disabled because WordPress instrumentation based on AST processing is not stable on CentOS with PHP 8.0 and 8.3' + . '; ELASTIC_APM_PHP_TESTS_MATRIX_ROW environment variable: ' . $componentTestingMatrixRow + ); + self::dummyAssert(); + return true; + } + + return false; + } } diff --git a/tests/ElasticApmTests/UnitTests/InferredSpansBuilderTest.php b/tests/ElasticApmTests/UnitTests/InferredSpansBuilderTest.php index bfacb222a..28e43bc26 100644 --- a/tests/ElasticApmTests/UnitTests/InferredSpansBuilderTest.php +++ b/tests/ElasticApmTests/UnitTests/InferredSpansBuilderTest.php @@ -337,6 +337,7 @@ function (TracerBuilderForTests $tracerBuilder) use ($inputOptions): void { // Enable span stack trace collection for span with any duration $tracerBuilder->withConfig(OptionNames::SPAN_STACK_TRACE_MIN_DURATION, '0'); foreach ($inputOptions as $optName => $optVal) { + /** @phpstan-ignore-next-line */ $tracerBuilder->withConfig($optName, strval($optVal)); } } diff --git a/tests/ElasticApmTests/UnitTests/UtilTests/StackTraceUtilTest.php b/tests/ElasticApmTests/UnitTests/UtilTests/StackTraceUtilTest.php index c08e04229..bda9daa80 100644 --- a/tests/ElasticApmTests/UnitTests/UtilTests/StackTraceUtilTest.php +++ b/tests/ElasticApmTests/UnitTests/UtilTests/StackTraceUtilTest.php @@ -85,6 +85,8 @@ public function testClosureExpections(): void $closureFrameExpections->assertMatches($actualStackTrace[0]); self::assertNull($actualStackTrace[0]->function); $thisFuncFrameExpections->assertMatches($actualStackTrace[1]); + + $dbgCtx->pop(); } public static function testStaticClosureExpections(): void @@ -143,6 +145,8 @@ public function testConvertClassAndMethodToFunctionName(?string $classicName, ?b $dbgCtx->add(['actualFuncName' => $actualFuncName]); self::assertSame($expectedFuncName, $actualFuncName); + + $dbgCtx->pop(); } /** @@ -207,6 +211,8 @@ public function testCaptureInApmFormatOneTestFame(MixedMap $testArgs): void self::assertNotNull($frame->function); self::assertStringNotContainsString(__FUNCTION__, $frame->function); } + + $dbgCtx->pop(); } /** @@ -336,6 +342,8 @@ public function testCaptureInApmFormatMultipleTestFrames(MixedMap $testArgs): vo self::assertNotNull($frame->function); self::assertStringNotContainsString(__FUNCTION__, $frame->function); } + + $dbgCtx->pop(); } /** @@ -493,6 +501,8 @@ public function testConvertPhpToApmFormat(MixedMap $testArgs): void $dbgCtx->add(['actualOutputFrames' => $actualOutputFrames]); $expectedOutput = $maxNumberOfFrames === null ? $fullExpectedOutput : array_slice($fullExpectedOutput, /* offset */ 0, /* length */ $maxNumberOfFrames); StackTraceExpectations::fromFramesExpectations($expectedOutput)->assertMatches($actualOutputFrames); + + $dbgCtx->pop(); } private const CALL_KINDS_SEQUENCE_KEY = 'call_kinds_sequence'; @@ -611,6 +621,8 @@ public function testCaptureInApmFormatOnDummyCode(MixedMap $testArgs): void $retVal = ($firstCall->callable)(...$firstCallArgs); $dbgCtx->add(['retVal' => $retVal]); StackTraceExpectations::fromFramesExpectations($retVal->expectations, /* allowToBePrefixOfActual */ true)->assertMatches($retVal->actual); + + $dbgCtx->pop(); } private const DEPTH_BEFORE_CALL_USER_FUNC_KEY = 'depth_before_call_user_func'; @@ -644,32 +656,39 @@ public function dataProviderForTestCaptureInApmFormatWithCallUserFunc(): iterabl private function helperForTestCaptureInApmFormatWithCallUserFunc(MixedMap $testArgs, int $depth, array $framesExpectations): array { AssertMessageStack::newScope(/* out */ $dbgCtx, AssertMessageStack::funcArgs()); - $thisFuncAsCallable = [__CLASS__, __FUNCTION__]; - $depthBeforeCallUserFunc = $testArgs->getInt(self::DEPTH_BEFORE_CALL_USER_FUNC_KEY); - $depthAfterCallUserFunc = $testArgs->getInt(self::DEPTH_AFTER_CALL_USER_FUNC_KEY); - self::assertLessThanOrEqual($depthBeforeCallUserFunc + $depthAfterCallUserFunc, $depth); - if ($depth === $depthBeforeCallUserFunc + $depthAfterCallUserFunc) { - $numberOfStackFramesToSkip = $testArgs->getPositiveOrZeroInt(self::NUMBER_OF_STACK_FRAMES_TO_SKIP_KEY); - array_unshift($framesExpectations, StackTraceFrameExpectations::fromLocationOnly(__FILE__, __LINE__ + 1)); - $actuallyCatpuredStackTrace = self::stackTraceUtil()->captureInApmFormat($numberOfStackFramesToSkip, /* maxNumberOfFrames */ null); - return [array_slice($framesExpectations, $numberOfStackFramesToSkip), $actuallyCatpuredStackTrace]; - } - if ($depth < $depthBeforeCallUserFunc || $depth > $depthBeforeCallUserFunc) { - array_unshift($framesExpectations, StackTraceFrameExpectations::fromClassMethod(__FILE__, __LINE__ + 1, __CLASS__, /* isStatic */ false, __FUNCTION__)); - return $this->helperForTestCaptureInApmFormatWithCallUserFunc($testArgs, $depth + 1, $framesExpectations); - } + try { + $thisFuncAsCallable = [__CLASS__, __FUNCTION__]; + $depthBeforeCallUserFunc = $testArgs->getInt(self::DEPTH_BEFORE_CALL_USER_FUNC_KEY); + $depthAfterCallUserFunc = $testArgs->getInt(self::DEPTH_AFTER_CALL_USER_FUNC_KEY); + self::assertLessThanOrEqual($depthBeforeCallUserFunc + $depthAfterCallUserFunc, $depth); + if ($depth === $depthBeforeCallUserFunc + $depthAfterCallUserFunc) { + $numberOfStackFramesToSkip = $testArgs->getPositiveOrZeroInt(self::NUMBER_OF_STACK_FRAMES_TO_SKIP_KEY); + array_unshift($framesExpectations, StackTraceFrameExpectations::fromLocationOnly(__FILE__, __LINE__ + 1)); + $actuallyCatpuredStackTrace = self::stackTraceUtil()->captureInApmFormat($numberOfStackFramesToSkip, /* maxNumberOfFrames */ null); + return [array_slice($framesExpectations, $numberOfStackFramesToSkip), $actuallyCatpuredStackTrace]; + } + if ($depth < $depthBeforeCallUserFunc || $depth > $depthBeforeCallUserFunc) { + array_unshift($framesExpectations, StackTraceFrameExpectations::fromClassMethod(__FILE__, __LINE__ + 1, __CLASS__, /* isStatic */ false, __FUNCTION__)); + return $this->helperForTestCaptureInApmFormatWithCallUserFunc($testArgs, $depth + 1, $framesExpectations); + } - self::assertSame($depthBeforeCallUserFunc, $depth); - $isCallUserFuncArrayVariant = $testArgs->getBool(self::IS_CALL_USER_FUNC_ARRAY_VARIANT_KEY); - $framesExpectationForCallToThisFuncByCallUserFunc = StackTraceFrameExpectations::fromClassMethodNoLocation(__CLASS__, /* isStatic */ false, __FUNCTION__); - $callUserFuncLine = __LINE__ + 6; - array_unshift($framesExpectations, StackTraceFrameExpectations::fromStandaloneFunction(__FILE__, $callUserFuncLine, $isCallUserFuncArrayVariant ? 'call_user_func_array' : 'call_user_func')); - array_unshift($framesExpectations, $framesExpectationForCallToThisFuncByCallUserFunc); - $callArgs = [$testArgs, $depth + 1, $framesExpectations]; - self::assertSame(__LINE__ + 2, $callUserFuncLine); - /** @var array{StackTraceFrameExpectations[], StackTraceFrame[]} $retVal */ - $retVal = $isCallUserFuncArrayVariant ? call_user_func_array($thisFuncAsCallable, $callArgs) : call_user_func($thisFuncAsCallable, ...$callArgs); - return $retVal; + self::assertSame($depthBeforeCallUserFunc, $depth); + $isCallUserFuncArrayVariant = $testArgs->getBool(self::IS_CALL_USER_FUNC_ARRAY_VARIANT_KEY); + $framesExpectationForCallToThisFuncByCallUserFunc = StackTraceFrameExpectations::fromClassMethodNoLocation(__CLASS__, /* isStatic */ false, __FUNCTION__); + $callUserFuncLine = __LINE__ + 9; // it should have the line number with call_user_func_array($thisFuncAsCallable) or call_user_func($thisFuncAsCallable) + array_unshift( + $framesExpectations, + StackTraceFrameExpectations::fromStandaloneFunction(__FILE__, $callUserFuncLine, $isCallUserFuncArrayVariant ? 'call_user_func_array' : 'call_user_func') + ); + array_unshift($framesExpectations, $framesExpectationForCallToThisFuncByCallUserFunc); + $callArgs = [$testArgs, $depth + 1, $framesExpectations]; + self::assertSame(__LINE__ + 2, $callUserFuncLine); + /** @var array{StackTraceFrameExpectations[], StackTraceFrame[]} $retVal */ + $retVal = $isCallUserFuncArrayVariant ? call_user_func_array($thisFuncAsCallable, $callArgs) : call_user_func($thisFuncAsCallable, ...$callArgs); + return $retVal; + } finally { + $dbgCtx->pop(); + } } /** @@ -678,12 +697,15 @@ private function helperForTestCaptureInApmFormatWithCallUserFunc(MixedMap $testA public function testCaptureInApmFormatWithCallUserFunc(MixedMap $testArgs): void { AssertMessageStack::newScope(/* out */ $dbgCtx, AssertMessageStack::funcArgs()); + $framesExpectations = [StackTraceFrameExpectations::fromClassMethodUnknownLocation(__CLASS__, /* isStatic */ false, 'testCaptureInApmFormatWithCallUserFunc')]; array_unshift($framesExpectations, StackTraceFrameExpectations::fromClassMethod(__FILE__, __LINE__ + 1, __CLASS__, /* isStatic */ false, 'helperForTestCaptureInApmFormatWithCallUserFunc')); $expectedActual = $this->helperForTestCaptureInApmFormatWithCallUserFunc($testArgs, /* depth */ 1, $framesExpectations); $dbgCtx->add(['expectedActual' => $expectedActual]); self::assertCount(2, $expectedActual); StackTraceExpectations::fromFramesExpectations($expectedActual[0], /* allowExpectedStackTraceToBePrefix */ true)->assertMatches($expectedActual[1]); + + $dbgCtx->pop(); } /** @@ -798,6 +820,8 @@ public function testCaptureInClassicFormatOneTestFrame(MixedMap $testArgs): void $frame = $actualCapturedStackTrace[0]; self::assertNotEquals(__FUNCTION__, $frame->function); } + + $dbgCtx->pop(); } /** @@ -943,6 +967,8 @@ public function testCaptureInClassicFormatMultipleTestFrames(MixedMap $testArgs) $frame = $actualCapturedStackTrace[$actualCapturedStackTraceFrameIndex]; self::assertNotEquals(__FUNCTION__, $frame->function); } + + $dbgCtx->pop(); } /** @@ -1078,6 +1104,8 @@ public function testConvertClassicToApmFormat(MixedMap $testArgs): void $dbgCtx->add(['actualOutputFrames' => $actualOutputFrames]); $expectedOutput = $maxNumberOfFrames === null ? $fullExpectedOutput : array_slice($fullExpectedOutput, /* offset */ 0, /* length */ $maxNumberOfFrames); StackTraceExpectations::fromFrames($expectedOutput)->assertMatches($actualOutputFrames); + + $dbgCtx->pop(); } /** @@ -1200,6 +1228,8 @@ public function testConvertThrowableTraceToApmFormat(MixedMap $testArgs): void self::assertNotNull($frame->function); self::assertStringNotContainsString(__FUNCTION__, $frame->function); } + + $dbgCtx->pop(); } public function testLimitConfigToMaxNumberOfFrames(): void diff --git a/tests/ElasticApmTests/UnitTests/WordPressAutoInstrumentationUnitTest.php b/tests/ElasticApmTests/UnitTests/WordPressAutoInstrumentationUnitTest.php index 7d3adc9b4..007c044e8 100644 --- a/tests/ElasticApmTests/UnitTests/WordPressAutoInstrumentationUnitTest.php +++ b/tests/ElasticApmTests/UnitTests/WordPressAutoInstrumentationUnitTest.php @@ -45,6 +45,8 @@ public function testFindAddonNameInFilePath(): void $dbgCtx->add(['actualGroupKind' => $actualGroupKind, 'actualGroupName' => $actualGroupName]); self::assertSame($expectedGroupKind, $actualGroupKind); self::assertSame($expectedGroupName, $actualGroupName); + + $dbgCtx->pop(); }; $testImpl = function (string $filePath, string $expectedGroupKind, ?string $expectedGroupName) use ($testImplFilePathAsIs): void { diff --git a/tests/ElasticApmTests/Util/AssertMessageStack.php b/tests/ElasticApmTests/Util/AssertMessageStack.php index 56883efa3..35fb1a374 100644 --- a/tests/ElasticApmTests/Util/AssertMessageStack.php +++ b/tests/ElasticApmTests/Util/AssertMessageStack.php @@ -145,12 +145,12 @@ public static function funcArgs(): array return $result; } - public function autoPopScope(AssertMessageStackScopeData $expectedTopData): void + public function popTopScope(AssertMessageStackScopeData $expectedTopScope): void { - $dbgCtx = ['this' => $this, 'expectedTopData' => $expectedTopData]; + $actualTopScope = $this->scopesStack[count($this->scopesStack) - 1]; + $dbgCtx = compact('actualTopScope', 'expectedTopScope', 'this'); Assert::assertNotEmpty($this->scopesStack, LoggableToString::convert($dbgCtx)); - $actualTopData = $this->scopesStack[count($this->scopesStack) - 1]; - Assert::assertSame($expectedTopData, $actualTopData, LoggableToString::convert($dbgCtx)); + Assert::assertSame($expectedTopScope, $actualTopScope, LoggableToString::convert($dbgCtx)); array_pop(/* ref */ $this->scopesStack); } @@ -187,6 +187,12 @@ public static function getContextsStack(): array return $result; } + public static function reset(): void + { + self::ensureSingleton()->scopesStack = []; + AssertMessageStackScopeData::$nextId = 1; + } + public function toLog(LogStreamInterface $stream): void { $stream->toLogAs(['scopesStack count' => count($this->scopesStack), 'isEnabled' => self::$isEnabled]); diff --git a/tests/ElasticApmTests/Util/AssertMessageStackScopeAutoRef.php b/tests/ElasticApmTests/Util/AssertMessageStackScopeAutoRef.php index bc2644665..836614475 100644 --- a/tests/ElasticApmTests/Util/AssertMessageStackScopeAutoRef.php +++ b/tests/ElasticApmTests/Util/AssertMessageStackScopeAutoRef.php @@ -40,12 +40,18 @@ public function __construct(AssertMessageStack $stack, ?AssertMessageStackScopeD } public function __destruct() + { + $this->pop(); + } + + public function pop(): void { if ($this->data === null) { return; } - $this->stack->autoPopScope($this->data); + $this->stack->popTopScope($this->data); + $this->data = null; } /** diff --git a/tests/ElasticApmTests/Util/AssertMessageStackScopeData.php b/tests/ElasticApmTests/Util/AssertMessageStackScopeData.php index 3ed63b159..38b6a85d2 100644 --- a/tests/ElasticApmTests/Util/AssertMessageStackScopeData.php +++ b/tests/ElasticApmTests/Util/AssertMessageStackScopeData.php @@ -25,11 +25,18 @@ use Elastic\Apm\Impl\Log\LoggableInterface; use Elastic\Apm\Impl\Log\LogStreamInterface; +use Elastic\Apm\Impl\Util\ArrayUtil; use Elastic\Apm\Impl\Util\DbgUtil; use PHPUnit\Framework\Assert; final class AssertMessageStackScopeData implements LoggableInterface { + /** @var int */ + public static $nextId = 1; + + /** @var int */ + public $id; + /** @var Pair>[] */ public $subScopesStack; @@ -39,6 +46,10 @@ final class AssertMessageStackScopeData implements LoggableInterface */ public function __construct(string $name, array $initialCtx) { + if (self::$nextId === PHP_INT_MAX) { + self::$nextId = 1; + } + $this->id = self::$nextId++; $this->subScopesStack = [new Pair($name, $initialCtx)]; } @@ -73,6 +84,7 @@ public static function buildContextName(int $numberOfStackFramesToSkip): string public function toLog(LogStreamInterface $stream): void { - $stream->toLogAs(['subScopesStack count' => count($this->subScopesStack)]); + $name = ArrayUtil::isEmpty($this->subScopesStack) ? 'N/A' : ArrayUtilForTests::getFirstValue($this->subScopesStack)->first; + $stream->toLogAs(['ID' => $this->id, 'name' => $name, 'subScopesStack count' => count($this->subScopesStack)]); } } diff --git a/tests/ElasticApmTests/Util/Deserialization/DeserializationException.php b/tests/ElasticApmTests/Util/Deserialization/DeserializationException.php index 6d0501d2f..cca039288 100644 --- a/tests/ElasticApmTests/Util/Deserialization/DeserializationException.php +++ b/tests/ElasticApmTests/Util/Deserialization/DeserializationException.php @@ -28,7 +28,7 @@ class DeserializationException extends RuntimeException { - public function __construct(string $message, int $code = 0, Throwable $previous = null) + public function __construct(string $message, int $code = 0, ?Throwable $previous = null) { parent::__construct($message, $code, $previous); } diff --git a/tests/ElasticApmTests/Util/Deserialization/DeserializationUtil.php b/tests/ElasticApmTests/Util/Deserialization/DeserializationUtil.php index fff1e4594..af90208e6 100644 --- a/tests/ElasticApmTests/Util/Deserialization/DeserializationUtil.php +++ b/tests/ElasticApmTests/Util/Deserialization/DeserializationUtil.php @@ -43,7 +43,7 @@ public static function buildUnknownKeyException($key): DeserializationException return DeserializationUtil::buildException('Unknown key: ' . $key); } - public static function buildException(?string $msgDetails = null, int $code = 0, Throwable $previous = null): DeserializationException + public static function buildException(?string $msgDetails = null, int $code = 0, ?Throwable $previous = null): DeserializationException { $msgStart = 'Deserialization failed'; if ($msgDetails !== null) { diff --git a/tests/ElasticApmTests/Util/Deserialization/ServerApiSchemaValidationException.php b/tests/ElasticApmTests/Util/Deserialization/ServerApiSchemaValidationException.php index 02945b045..5b53c6c58 100644 --- a/tests/ElasticApmTests/Util/Deserialization/ServerApiSchemaValidationException.php +++ b/tests/ElasticApmTests/Util/Deserialization/ServerApiSchemaValidationException.php @@ -28,7 +28,7 @@ class ServerApiSchemaValidationException extends RuntimeException { - public function __construct(string $message, int $code = 0, Throwable $previous = null) + public function __construct(string $message, int $code = 0, ?Throwable $previous = null) { parent::__construct($message, $code, $previous); } diff --git a/tests/ElasticApmTests/Util/DummyExceptionForTests.php b/tests/ElasticApmTests/Util/DummyExceptionForTests.php index 577e8c8db..00f1f19e6 100644 --- a/tests/ElasticApmTests/Util/DummyExceptionForTests.php +++ b/tests/ElasticApmTests/Util/DummyExceptionForTests.php @@ -31,7 +31,7 @@ class DummyExceptionForTests extends RuntimeException public const NAMESPACE = __NAMESPACE__; public const FQ_CLASS_NAME = __CLASS__; - public function __construct(string $message, int $code = 0, Throwable $previous = null) + public function __construct(string $message, int $code = 0, ?Throwable $previous = null) { parent::__construct($message, $code, $previous); } diff --git a/tests/ElasticApmTests/Util/InvalidEventDataException.php b/tests/ElasticApmTests/Util/InvalidEventDataException.php index 4571ece0b..6dc3198f4 100644 --- a/tests/ElasticApmTests/Util/InvalidEventDataException.php +++ b/tests/ElasticApmTests/Util/InvalidEventDataException.php @@ -28,7 +28,7 @@ class InvalidEventDataException extends RuntimeException { - public function __construct(string $message, int $code = 0, Throwable $previous = null) + public function __construct(string $message, int $code = 0, ?Throwable $previous = null) { parent::__construct($message, $code, $previous); } diff --git a/tests/ElasticApmTests/Util/LogSinkForTests.php b/tests/ElasticApmTests/Util/LogSinkForTests.php index 6b6f8af71..0cae6e3ea 100644 --- a/tests/ElasticApmTests/Util/LogSinkForTests.php +++ b/tests/ElasticApmTests/Util/LogSinkForTests.php @@ -35,20 +35,29 @@ */ final class LogSinkForTests extends SinkBase { + /** @var ?bool */ + private static $isStderrDefined = null; + /** @var string */ private $dbgProcessName; - /** @var bool */ - private $isStderrDefined; - public function __construct(string $dbgProcessName) { $this->dbgProcessName = $dbgProcessName; + } - if (!defined('STDERR')) { - define('STDERR', fopen('php://stderr', 'w')); + private static function ensureStdErrIsDefined(): bool + { + if (self::$isStderrDefined === null) { + if (defined('STDERR')) { + self::$isStderrDefined = true; + } else { + define('STDERR', fopen('php://stderr', 'w')); + self::$isStderrDefined = defined('STDERR'); + } } - $this->isStderrDefined = defined('STDERR'); + + return self::$isStderrDefined; } protected function consumePreformatted( @@ -70,6 +79,13 @@ protected function consumePreformatted( $this->consumeFormatted($statementLevel, $formattedRecord); } + public static function writeLineToStdErr(string $text): void + { + if (self::ensureStdErrIsDefined()) { + fwrite(STDERR, $text . PHP_EOL); + } + } + private function consumeFormatted(int $statementLevel, string $statementText): void { if (OsUtilForTests::isWindows()) { @@ -80,9 +96,7 @@ private function consumeFormatted(int $statementLevel, string $statementText): v syslog(self::levelToSyslog($statementLevel), $statementText); } - if ($this->isStderrDefined) { - fwrite(STDERR, $statementText . PHP_EOL); - } + self::writeLineToStdErr($statementText); } public static function levelToString(int $level): string diff --git a/tests/ElasticApmTests/Util/MixedMap.php b/tests/ElasticApmTests/Util/MixedMap.php index 6815f8bff..26836503c 100644 --- a/tests/ElasticApmTests/Util/MixedMap.php +++ b/tests/ElasticApmTests/Util/MixedMap.php @@ -194,6 +194,7 @@ public function getNullablePositiveOrZeroInt(string $key): ?int TestCaseBase::assertGreaterThanOrEqual(0, $value); } /** @var null|positive-int|0 $value */ + $dbgCtx->pop(); return $value; } @@ -216,6 +217,7 @@ public function getPositiveOrZeroInt(string $key): int $value = $this->getInt($key); TestCaseBase::assertGreaterThanOrEqual(0, $value); /** @var positive-int|0 $value */ + $dbgCtx->pop(); return $value; } diff --git a/tests/ElasticApmTests/Util/PhpUnitExtensionBase.php b/tests/ElasticApmTests/Util/PhpUnitExtensionBase.php index 868258ea8..f917926cf 100644 --- a/tests/ElasticApmTests/Util/PhpUnitExtensionBase.php +++ b/tests/ElasticApmTests/Util/PhpUnitExtensionBase.php @@ -64,6 +64,7 @@ public function __construct(string $dbgProcessName) */ public function executeBeforeTest(string $test): void { + AssertMessageStack::reset(); self::$timestampBeforeTest = AmbientContextForTests::clock()->getSystemClockCurrentTime(); ($loggerProxy = $this->logger->ifDebugLevelEnabled(__LINE__, __FUNCTION__)) && $loggerProxy->includeStackTrace()->log('', ['timestampBeforeTest' => TimeUtilForTests::timestampToLoggable(self::$timestampBeforeTest)]); diff --git a/tests/ElasticApmTests/Util/StackTraceExpectations.php b/tests/ElasticApmTests/Util/StackTraceExpectations.php index aafa4a773..03c34c6e4 100644 --- a/tests/ElasticApmTests/Util/StackTraceExpectations.php +++ b/tests/ElasticApmTests/Util/StackTraceExpectations.php @@ -89,5 +89,7 @@ public function assertMatches(array $actual): void } else { TestCaseBase::assertSame(count($this->frames), count($actual)); } + + $dbgCtx->pop(); } } diff --git a/tests/ElasticApmTests/Util/StackTraceFrameExpectations.php b/tests/ElasticApmTests/Util/StackTraceFrameExpectations.php index cf4b8f4b9..295a271b2 100644 --- a/tests/ElasticApmTests/Util/StackTraceFrameExpectations.php +++ b/tests/ElasticApmTests/Util/StackTraceFrameExpectations.php @@ -38,6 +38,9 @@ final class StackTraceFrameExpectations extends ExpectationsBase /** @var Optional */ public $function; + /** @var bool */ + public $isFunctionRegex = false; + /** @var Optional */ public $lineno; @@ -101,9 +104,35 @@ public static function fromStandaloneFunction(string $fileName, int $lineNumber, return $result; } + private static function convertFunctionNameToRegPattern(string $text): string + { + $result = $text; + $result = str_replace('\\', '\\\\', $result); + $result = str_replace('{', '\\{', $result); + $result = str_replace('}', '\\}', $result); + $result = str_replace('(', '\\(', $result); + $result = str_replace(')', '\\)', $result); + return '/^' . $result . '$/'; + } + public static function fromClosure(string $fileName, int $lineNumber, ?string $namespace, string $class, bool $isStatic): self { - $result = self::fromClassMethodUnknownLocation($class, $isStatic, ($namespace === null ? '' : ($namespace . '\\')) . '{closure}'); + // Before PHP 8.4: ElasticApmTests\\TestsSharedCode\\SpanStackTraceTestSharedCode::ElasticApmTests\\TestsSharedCode\\{closure} + // PHP 8.4: ElasticApmTests\\TestsSharedCode\\SpanStackTraceTestSharedCode::{closure:ElasticApmTests\\TestsSharedCode\\SpanStackTraceTestSharedCode::allSpanCreatingApis():207} + if (PHP_VERSION_ID < 80400) { + $result = self::fromClassMethodUnknownLocation($class, $isStatic, ($namespace === null ? '' : ($namespace . '\\')) . '{closure}'); + } else { + $result = self::fromClassMethodUnknownLocation($class, $isStatic, '{closure:__CLASS__::__METHOD__():__LINE__}'); + $expectedFunc = $result->function->getValue(); + TestCaseBase::assertNotNull($expectedFunc); + $expectedFuncRegex = self::convertFunctionNameToRegPattern($expectedFunc); + $expectedFuncRegex = str_replace('__CLASS__', '[a-zA-Z0-9\\\\]+', $expectedFuncRegex); + $expectedFuncRegex = str_replace('__METHOD__', '[a-zA-Z0-9]+', $expectedFuncRegex); + $expectedFuncRegex = str_replace('__LINE__', '[0-9]+', $expectedFuncRegex); + $result->function->setValue($expectedFuncRegex); + $result->isFunctionRegex = true; + } + $result->filename->setValue($fileName); $result->lineno->setValue($lineNumber); return $result; @@ -143,7 +172,22 @@ public function assertMatches(StackTraceFrame $actual): void $dbgCtx->add(['this' => $this]); TestCaseBase::assertSameExpectedOptional($this->filename, $actual->filename); - TestCaseBase::assertSameExpectedOptional($this->function, $actual->function); + if ($this->isFunctionRegex) { + if ($this->function->isValueSet()) { + $expectedFuncRegex = $this->function->getValue(); + $actualFunc = $actual->function; + if ($expectedFuncRegex === null) { + TestCaseBase::assertNull($actualFunc); + } else { + TestCaseBase::assertNotNull($actualFunc); + TestCaseBase::assertMatchesRegularExpression($expectedFuncRegex, $actualFunc); + } + } + } else { + TestCaseBase::assertSameExpectedOptional($this->function, $actual->function); + } TestCaseBase::assertSameExpectedOptional($this->lineno, $actual->lineno); + + $dbgCtx->pop(); } } diff --git a/tests/ElasticApmTests/Util/TestCaseBase.php b/tests/ElasticApmTests/Util/TestCaseBase.php index e121509d3..46870b5f5 100644 --- a/tests/ElasticApmTests/Util/TestCaseBase.php +++ b/tests/ElasticApmTests/Util/TestCaseBase.php @@ -85,7 +85,7 @@ public static function assertThrows( string $class, callable $execute, string $message = '', - callable $inspect = null + ?callable $inspect = null ): void { try { $execute(); @@ -140,6 +140,7 @@ public static function assertSameEx($expected, $actual, string $message = ''): v return is_float($value) || is_int($value); }; if ($isNumeric($expected) && $isNumeric($actual) && (is_float($expected) !== is_float($actual))) { + /** @phpstan-ignore-next-line */ self::assertSame(floatval($expected), floatval($actual), $message); } else { self::assertSame($expected, $actual, $message); @@ -748,6 +749,7 @@ public static function assertCountAtLeast(int $expectedMinCount, $haystack): voi { AssertMessageStack::newScope(/* out */ $dbgCtx, AssertMessageStack::funcArgs()); self::assertGreaterThanOrEqual($expectedMinCount, count($haystack)); + $dbgCtx->pop(); } /** @@ -820,6 +822,8 @@ public static function assertSame($expected, $actual, string $message = ''): voi self::addMessageStackToException($ex); throw $ex; } + + $dbgCtx->pop(); } /** @@ -1132,6 +1136,23 @@ public static function assertContainsEx($needle, iterable $haystack, string $mes } } + /** + * @inheritDoc + */ + public static function assertMatchesRegularExpression(string $pattern, string $string, string $message = ''): void + { + AssertMessageStack::newScope(/* out */ $dbgCtx, AssertMessageStack::funcArgs()); + + try { + $pregMatchRetVal = preg_match($pattern, $string); + $dbgCtx->add(compact('pregMatchRetVal')); + Assert::assertTrue($pregMatchRetVal > 0, $message); + } catch (AssertionFailedError $ex) { + self::addMessageStackToException($ex); + throw $ex; + } + } + public static function assertDirectoryDoesNotExist(string $directory, string $message = ''): void { /** diff --git a/tests/ElasticApmTests/Util/TextUtilForTests.php b/tests/ElasticApmTests/Util/TextUtilForTests.php index 57ca99839..c9ffd7ca4 100644 --- a/tests/ElasticApmTests/Util/TextUtilForTests.php +++ b/tests/ElasticApmTests/Util/TextUtilForTests.php @@ -123,6 +123,7 @@ public static function combineWithSeparatorIfNotEmpty(string $separator, string */ public static function emptyIfNull($input): string { + /** @phpstan-ignore-next-line */ return $input === null ? '' : strval($input); } } diff --git a/tests/bootstrap.php b/tests/bootstrap.php index 795658bf1..ed22184c2 100644 --- a/tests/bootstrap.php +++ b/tests/bootstrap.php @@ -21,15 +21,17 @@ declare(strict_types=1); -error_reporting(E_ALL | E_STRICT); - use ElasticApmTests\TestsRootDir; // Ensure that composer has installed all dependencies if (!file_exists(dirname(__DIR__) . '/composer.lock')) { - die("Dependencies must be installed using composer\n"); + die('Dependencies must be installed using composer' . PHP_EOL); } +// Disable deprecation notices starting from PHP 8.4 +// Deprecated: funcAbc(): Implicitly marking parameter $xyz as nullable is deprecated, the explicit nullable type must be used instead +error_reporting(PHP_VERSION_ID < 80400 ? E_ALL : (E_ALL & ~E_DEPRECATED)); + require __DIR__ . '/../vendor/autoload.php'; require __DIR__ . '/polyfills/load.php'; diff --git a/tests/polyfills/load.php b/tests/polyfills/load.php index 4abcd45bd..ef3a9a4dd 100644 --- a/tests/polyfills/load.php +++ b/tests/polyfills/load.php @@ -21,8 +21,6 @@ declare(strict_types=1); -error_reporting(E_ALL | E_STRICT); - if (!function_exists('array_key_first')) { require __DIR__ . '/array_key_first.php'; } diff --git a/updatecli-compose.yaml b/updatecli-compose.yaml new file mode 100644 index 000000000..5d483d00a --- /dev/null +++ b/updatecli-compose.yaml @@ -0,0 +1,23 @@ +# Config file for `updatecli compose ...`. +# https://www.updatecli.io/docs/core/compose/ +policies: + - name: Handle apm-data server specs + policy: ghcr.io/elastic/oblt-updatecli-policies/apm/apm-data-spec:0.6.0@sha256:c0bbdec23541bed38df1342c95aeb601530a113db1ff11715c1c7616ed5e9e8b + values: + - .ci/updatecli/values.d/scm.yml + - .ci/updatecli/values.d/apm-data-spec.yml + - name: Handle apm gherkin specs + policy: ghcr.io/elastic/oblt-updatecli-policies/apm/apm-gherkin:0.6.0@sha256:dbaf4d855c5c212c3b5a8d2cc98c243a2b769ac347198ae8814393a1a0576587 + values: + - .ci/updatecli/values.d/scm.yml + - .ci/updatecli/values.d/apm-gherkin.yml + - name: Handle apm json specs + policy: ghcr.io/elastic/oblt-updatecli-policies/apm/apm-json-specs:0.6.0@sha256:e5a74c159ceed02fd20515ea76fa25ff81e3ccf977e74e636f9973db86aa52a5 + values: + - .ci/updatecli/values.d/scm.yml + - .ci/updatecli/values.d/apm-json-specs.yml + - name: Update Updatecli policies + policy: ghcr.io/updatecli/policies/autodiscovery/updatecli:0.8.0@sha256:99e9e61b501575c2c176c39f2275998d198b590a3f6b1fe829f7315f8d457e7f + values: + - .ci/updatecli/values.d/scm.yml + - .ci/updatecli/values.d/update-compose.yml