-
Notifications
You must be signed in to change notification settings - Fork 17.4k
fix(elasticsearch): pin compatible-with at the transport layer to keep ES 8 servers working #66065
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
eladkal
merged 12 commits into
apache:main
from
petercheon:fix/elasticsearch-compat-with-v2
May 19, 2026
Merged
Changes from all commits
Commits
Show all changes
12 commits
Select commit
Hold shift + click to select a range
c0ef998
fix(elasticsearch): pin compatible-with at the transport layer to kee…
peter-cheon bf7cbc1
fixup(elasticsearch): tighten apply_compat_with — idempotent guard, s…
peter-cheon 780f963
fixup(elasticsearch): mirror upstream selective mimetype rewrite
peter-cheon 879b864
fixup(elasticsearch): import AirflowConfigException via common.compat…
peter-cheon 44d5c77
Merge branch 'main' into fix/elasticsearch-compat-with-v2
petercheon d360eca
Merge branch 'main' into fix/elasticsearch-compat-with-v2
petercheon 9b7da4a
Merge branch 'main' into fix/elasticsearch-compat-with-v2
petercheon 964f804
Merge branch 'main' into fix/elasticsearch-compat-with-v2
petercheon 028717f
fixup(elasticsearch): satisfy CI lint, mypy and project-structure tests
peter-cheon 32e22cf
Merge branch 'main' into fix/elasticsearch-compat-with-v2
petercheon 5d8b8f9
Address review feedback: tolerant transport wrapper + clarified docs
peter-cheon 64adc81
Add 'misconfiguration' to spelling wordlist
peter-cheon File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -1031,6 +1031,7 @@ milli | |
| millis | ||
| milton | ||
| minikube | ||
| misconfiguration | ||
| misconfigured | ||
| Mixin | ||
| mixin | ||
|
|
||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
119 changes: 119 additions & 0 deletions
119
providers/elasticsearch/src/airflow/providers/elasticsearch/_compat.py
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,119 @@ | ||
| # | ||
| # Licensed to the Apache Software Foundation (ASF) under one | ||
| # or more contributor license agreements. See the NOTICE file | ||
| # distributed with this work for additional information | ||
| # regarding copyright ownership. The ASF 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. | ||
| """ | ||
| Helpers shared between the Elasticsearch hooks and log handler. | ||
|
|
||
| Currently this exposes a single helper, :func:`apply_compat_with`, that lets the | ||
| provider keep working against an Elasticsearch server whose major version does | ||
| not match the installed ``elasticsearch`` Python client major. See the helper | ||
| docstring for the regression context. | ||
| """ | ||
|
|
||
| from __future__ import annotations | ||
|
|
||
| import functools | ||
| import re | ||
| from typing import TYPE_CHECKING | ||
|
|
||
| from airflow.providers.common.compat.sdk import AirflowConfigException, conf | ||
|
|
||
| if TYPE_CHECKING: | ||
| import elasticsearch | ||
|
|
||
|
|
||
| # Matches the JSON / NDJSON / mapbox vector-tile mimetypes the ``elasticsearch`` | ||
| # client negotiates, in either form: the raw ``application/json`` (what the | ||
| # generated client code writes into ``__headers``) and the already-rewritten | ||
| # ``application/vnd.elasticsearch+json; compatible-with=<N>`` (what | ||
| # ``mimetype_header_to_compat`` in ``elasticsearch/_sync/client/_base.py`` | ||
| # emits before handing the request to the transport). Both forms must be | ||
| # rewritten to ``compatible-with=<configured>``; anything else (notably | ||
| # ``text/plain`` used by the cat APIs) is left intact, mirroring upstream's | ||
| # selective substitution behaviour. | ||
| _COMPAT_MIMETYPE_RE = re.compile( | ||
| r"application/(?:vnd\.elasticsearch\+)?(json|x-ndjson|vnd\.mapbox-vector-tile)" | ||
| r"(?:\s*;\s*compatible-with=\d+)?" | ||
| ) | ||
| _COMPAT_MAJOR_RE = re.compile(r"^\d+$") | ||
|
|
||
|
|
||
| def apply_compat_with(client: elasticsearch.Elasticsearch) -> elasticsearch.Elasticsearch: | ||
| """ | ||
| Pin the ``compatible-with`` HTTP content-negotiation level for ``client``. | ||
|
|
||
| The ``elasticsearch`` Python client always negotiates ``compatible-with=<client_major>`` | ||
| on every request; an Elasticsearch server with a different major version rejects | ||
| the request with HTTP 400 ``media_type_header_exception``. This is what happens | ||
| when an ``elasticsearch>=9`` client (the current default) talks to an | ||
| Elasticsearch 8.x server, which broke remote logging for ES 8 deployments | ||
| starting with provider 6.5.1. | ||
|
|
||
| When ``[elasticsearch] es_compat_with`` is set to a major version string | ||
| (e.g. ``"7"``, ``"8"``, ``"9"``) this helper wraps the client's transport | ||
| so every outbound request rewrites the ``compatible-with=<N>`` parameter on | ||
| the JSON / NDJSON / mapbox vector-tile parts of the ``Accept`` and | ||
| ``Content-Type`` headers. Non-JSON parts (notably ``text/plain`` used by | ||
| the cat APIs) are preserved verbatim, mirroring how elasticsearch-py's own | ||
| ``mimetype_header_to_compat`` handles content negotiation. | ||
|
|
||
| When the option is unset the client is returned unchanged and behaves | ||
| exactly as before. | ||
| """ | ||
| raw = conf.get("elasticsearch", "es_compat_with", fallback=None) | ||
| compat = (raw or "").strip() | ||
| if not compat: | ||
| return client | ||
| if not _COMPAT_MAJOR_RE.match(compat): | ||
| raise AirflowConfigException( | ||
| "[elasticsearch] es_compat_with must be a positive integer major version " | ||
| f"(e.g. '7', '8', '9'); got {raw!r}." | ||
| ) | ||
|
|
||
| transport = client.transport | ||
| if "perform_request" in transport.__dict__: | ||
| # Already wrapped on this transport instance — no-op so repeated calls | ||
| # to ``apply_compat_with`` (e.g. across hook reuse) stay idempotent. | ||
| return client | ||
|
|
||
| sub = rf"application/vnd.elasticsearch+\g<1>; compatible-with={compat}" | ||
| original_perform_request = transport.perform_request | ||
|
|
||
| # Accept ``*args, **kwargs`` so the wrapper survives future elastic_transport | ||
| # ``perform_request`` signature changes (new keyword-only params, reordered | ||
| # positionals). ``functools.wraps`` preserves the original ``__name__`` / | ||
| # ``__doc__`` / ``__wrapped__`` for tooling and introspection. | ||
| @functools.wraps(original_perform_request) | ||
| def perform_request(*args, **kwargs): # type: ignore[no-untyped-def] | ||
| headers = kwargs.get("headers") | ||
| if not headers: | ||
| return original_perform_request(*args, **kwargs) | ||
| # Walk every key case-insensitively so a future elastic_transport that | ||
| # forwards PascalCase headers does not silently bypass the rewrite. | ||
| merged = dict(headers) | ||
| for key in list(merged): | ||
| if key.lower() in ("accept", "content-type") and merged[key]: | ||
| merged[key] = _COMPAT_MIMETYPE_RE.sub(sub, merged[key]) | ||
| kwargs["headers"] = merged | ||
| return original_perform_request(*args, **kwargs) | ||
|
|
||
| # ``setattr`` instead of direct attribute assignment so mypy does not flag a | ||
| # ``method-assign`` error — we are *intentionally* shadowing the bound method | ||
| # at the instance level (the idempotency guard above checks the instance | ||
| # ``__dict__``). | ||
| setattr(transport, "perform_request", perform_request) | ||
| return client |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.