Skip to content

Commit a620419

Browse files
authored
Merge pull request #1 from kube-logging/fix/es9
feat: es 7-8 compat with Plugin and API version 9
2 parents b892496 + f711c43 commit a620419

File tree

9 files changed

+737
-74
lines changed

9 files changed

+737
-74
lines changed

.github/workflows/issue-auto-closer.yml

Lines changed: 0 additions & 12 deletions
This file was deleted.

.github/workflows/linux.yml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
name: Testing on Ubuntu
22
on:
3-
- push
43
- pull_request
54
permissions:
65
contents: read

.github/workflows/macos.yml

Lines changed: 0 additions & 29 deletions
This file was deleted.

.github/workflows/windows.yml

Lines changed: 0 additions & 29 deletions
This file was deleted.

README.compat.md

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
# Compat
2+
3+
## Mixed Elasticsearch Version Environments
4+
5+
### Compatibility Matrix
6+
7+
| Elasticsearch Gem | ES 7.x Server | ES 8.x Server | ES 9.x Server |
8+
|-------------------|---------------|---------------|---------------|
9+
| gem 7.17.x | ✅ Works | ✅ Works | ❌ No |
10+
| gem 8.15.x | ✅ Works | ✅ Works | ❌ No |
11+
| gem 9.1.x + patch | ✅ With config| ✅ With config| ✅ Works |
12+
13+
### Configuration Options
14+
15+
#### force_content_type
16+
17+
Type: String
18+
Default: nil (auto-detect)
19+
Valid values: "application/json", "application/x-ndjson"
20+
Manually override the Content-Type header. Required when using elasticsearch gem 9.x with ES 7.x or 8.x servers.
21+
22+
```ruby
23+
<match **>
24+
@type elasticsearch
25+
force_content_type application/json
26+
</match>
27+
```
28+
29+
#### ignore_version_content_type_mismatch
30+
31+
Type: Bool
32+
Default: false
33+
Automatically fallback to application/json if Content-Type version mismatch occurs. Enables seamless operation across mixed ES 7/8/9 environments.
34+
35+
Example:
36+
37+
```ruby
38+
<match **>
39+
@type elasticsearch
40+
force_content_type application/json
41+
ignore_version_content_type_mismath true
42+
</match>
43+
```
44+
45+
### Recommended Configuration
46+
47+
#### For ES 7/8 environments (Recommended)
48+
49+
Use elasticsearch gem 8.x - works with both versions, no configuration needed:
50+
51+
```ruby
52+
# Gemfile
53+
gem 'elasticsearch', '~> 8.15.0'
54+
55+
# fluent.conf
56+
<match **>
57+
@type elasticsearch
58+
hosts es7:9200,es8:9200
59+
# No special config needed!
60+
</match>
61+
```
62+
63+
#### For gem 9.x with ES 7/8 (Not recommended, but supported)
64+
65+
```ruby
66+
# Gemfile
67+
gem 'elasticsearch', '~> 9.1.0'
68+
69+
# fluent.conf
70+
<match **>
71+
@type elasticsearch
72+
hosts es7:9200,es8:9200
73+
74+
# REQUIRED: Force compatible content type
75+
force_content_type application/json
76+
</match>
77+
```
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
# Elasticsearch API 9.x Compatibility Patch
2+
#
3+
# Fixes crashes in elasticsearch-api gem 9.1.2 when connecting to ES 7.x/8.x servers.
4+
#
5+
# Bug: The gem expects ES 9 headers and crashes with NoMethodError when they're nil
6+
#
7+
# This patch is only needed if using elasticsearch gem 9.x
8+
# Not needed if using elasticsearch gem 7.x or 8.x
9+
10+
require 'elasticsearch/api'
11+
12+
module Elasticsearch
13+
module API
14+
module Utils
15+
class << self
16+
if method_defined?(:update_ndjson_headers!)
17+
alias_method :original_update_ndjson_headers!, :update_ndjson_headers!
18+
19+
def update_ndjson_headers!(headers, client_headers)
20+
return headers unless client_headers.is_a?(Hash)
21+
22+
current_content = client_headers.keys.find { |c| c.to_s.match?(/content[-_]?type/i) }
23+
return headers unless current_content
24+
25+
content_value = client_headers[current_content]
26+
return headers unless content_value
27+
28+
# ES 7/8 compatibility: Only process ES9-specific headers
29+
# If no "compatible-with" present, this is ES 7/8 format
30+
return headers unless content_value.to_s.include?('compatible-with')
31+
32+
# ES 9 detected, safe to call original
33+
original_update_ndjson_headers!(headers, client_headers)
34+
rescue StandardError => e
35+
warn "[elasticsearch-api-compat] Failed to update headers: #{e.class} - #{e.message}"
36+
headers
37+
end
38+
end
39+
end
40+
end
41+
end
42+
end

lib/fluent/plugin/out_elasticsearch.rb

Lines changed: 33 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
require_relative 'elasticsearch_index_lifecycle_management'
2828
require_relative 'elasticsearch_tls'
2929
require_relative 'elasticsearch_fallback_selector'
30+
require_relative 'elasticsearch_api_bugfix'
3031
begin
3132
require_relative 'oj_serializer'
3233
rescue LoadError
@@ -177,6 +178,12 @@ def initialize(retry_stream)
177178
config_param :use_legacy_template, :bool, :default => true
178179
config_param :catch_transport_exception_on_retry, :bool, :default => true
179180
config_param :target_index_affinity, :bool, :default => false
181+
config_param :force_content_type, :string, :default => nil,
182+
:desc => "Force specific Content-Type header (e.g., 'application/json' or 'application/x-ndjson'). " \
183+
"Overrides automatic version detection. Useful for mixed ES environments."
184+
config_param :ignore_version_content_type_mismatch, :bool, :default => false,
185+
:desc => "Automatically fallback to application/json if Content-Type version mismatch occurs. " \
186+
"Enables seamless operation across mixed ES 7/8/9 environments."
180187

181188
config_section :metadata, param_name: :metainfo, multi: false do
182189
config_param :include_chunk_id, :bool, :default => false
@@ -361,14 +368,31 @@ class << self
361368
@type_name = nil
362369
end
363370
@accept_type = nil
364-
if @content_type != ES9_CONTENT_TYPE
371+
372+
# Only set ES9 content type if not overridden and mismatch handling is not enabled
373+
if @content_type.to_s != ES9_CONTENT_TYPE && !@ignore_version_content_type_mismatch
365374
log.trace "Detected ES 9.x or above: Content-Type will be adjusted."
366375
@content_type = ES9_CONTENT_TYPE
367376
@accept_type = ES9_CONTENT_TYPE
377+
elsif @ignore_version_content_type_mismatch
378+
log.info "Ignoring ES version for Content-Type, using application/json for compatibility"
379+
@content_type = :'application/json'
380+
@accept_type = nil
368381
end
369382
end
370383
end
371384

385+
if @content_type.nil?
386+
log.warn "content_type was nil, defaulting to application/json"
387+
@content_type = :'application/json'
388+
end
389+
390+
if @force_content_type
391+
log.info "Forcing Content-Type to: #{@force_content_type}"
392+
@content_type = @force_content_type
393+
@accept_type = nil
394+
end
395+
372396
if @validate_client_version && !dry_run?
373397
if @last_seen_major_version != client_library_version.to_i
374398
raise Fluent::ConfigError, <<-EOC
@@ -623,11 +647,17 @@ def client(host = nil, compress_connection = false)
623647
else
624648
{}
625649
end
626-
headers = { 'Content-Type' => @content_type.to_s }
650+
651+
content_type_value = @content_type ? @content_type.to_s : 'application/json'
652+
accept_type_value = @accept_type ? @accept_type.to_s : nil
653+
content_type_value = 'application/json' if content_type_value.strip.empty?
654+
655+
headers = { 'Content-Type' => content_type_value }
627656
.merge(@custom_headers)
628657
.merge(@api_key_header)
629658
.merge(gzip_headers)
630-
headers.merge!('Accept' => @accept_type) if @accept_type
659+
660+
headers.merge!('Accept' => accept_type_value) if accept_type_value && !accept_type_value.strip.empty?
631661

632662
ssl_options = { verify: @ssl_verify, ca_file: @ca_file}.merge(@ssl_version_options)
633663

test/es-compat/docker-compose.yaml

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
services:
2+
elasticsearch7:
3+
image: docker.elastic.co/elasticsearch/elasticsearch:7.17.16
4+
container_name: es7
5+
environment:
6+
- discovery.type=single-node
7+
- "ES_JAVA_OPTS=-Xms512m -Xmx512m"
8+
- xpack.security.enabled=false
9+
ports:
10+
- "9207:9200"
11+
networks:
12+
- elastic
13+
14+
elasticsearch8:
15+
image: docker.elastic.co/elasticsearch/elasticsearch:8.12.0
16+
container_name: es8
17+
environment:
18+
- discovery.type=single-node
19+
- "ES_JAVA_OPTS=-Xms512m -Xmx512m"
20+
- xpack.security.enabled=false
21+
ports:
22+
- "9208:9200"
23+
networks:
24+
- elastic
25+
26+
elasticsearch9:
27+
image: docker.elastic.co/elasticsearch/elasticsearch:9.1.5
28+
container_name: es9
29+
environment:
30+
- discovery.type=single-node
31+
- "ES_JAVA_OPTS=-Xms512m -Xmx512m"
32+
- xpack.security.enabled=false
33+
ports:
34+
- "9209:9200"
35+
networks:
36+
- elastic
37+
38+
networks:
39+
elastic:
40+
driver: bridge

0 commit comments

Comments
 (0)