diff --git a/doc/api/settings.md b/doc/api/settings.md index 0a755fe104d5ec3d86b0041a6d19c64f16a0621a..3d599ff344708059066b1c0d85854d894e696741 100644 --- a/doc/api/settings.md +++ b/doc/api/settings.md @@ -568,7 +568,7 @@ to configure other related settings. These requirements are | `elasticsearch_namespace_ids` | array of integers | no | The namespaces to index via Elasticsearch if `elasticsearch_limit_indexing` is enabled. Premium and Ultimate only. | | `elasticsearch_project_ids` | array of integers | no | The projects to index via Elasticsearch if `elasticsearch_limit_indexing` is enabled. Premium and Ultimate only. | | `elasticsearch_search` | boolean | no | Enable Elasticsearch search. Premium and Ultimate only. | -| `elasticsearch_url` | string | no | The URL to use for connecting to Elasticsearch. Use a comma-separated list to support cluster (for example, `http://localhost:9200, http://localhost:9201"`). Premium and Ultimate only. | +| `elasticsearch_url` | string or array of strings | no | The URL to use for connecting to Elasticsearch. Use a comma-separated list or an array to support cluster (for example, `http://localhost:9200, http://localhost:9201` or `["http://localhost:9200", "http://localhost:9201"]`). Premium and Ultimate only. | | `elasticsearch_username` | string | no | The `username` of your Elasticsearch instance. Premium and Ultimate only. | | `elasticsearch_password` | string | no | The password of your Elasticsearch instance. Premium and Ultimate only. | | `elasticsearch_prefix` | string | no | Custom prefix for Elasticsearch index names. Defaults to `gitlab`. Must be 1-100 characters, contain only lowercase alphanumeric characters, hyphens, and underscores, and cannot start or end with a hyphen or underscore. Premium and Ultimate only. | diff --git a/ee/app/models/ee/application_setting.rb b/ee/app/models/ee/application_setting.rb index 25b113db46c2f4ca7e0ecfe1be17a39f2508a553..7c94faab5660e9e10dea79f764055e03443c74b1 100644 --- a/ee/app/models/ee/application_setting.rb +++ b/ee/app/models/ee/application_setting.rb @@ -618,7 +618,16 @@ def elasticsearch_url end def elasticsearch_url=(values) - cleaned = values.split(',').map { |url| url.strip.gsub(%r{/*\z}, '') } + urls = case values + when String + values.split(',') + when Array + values.flat_map { |v| v.to_s.split(',') } + else + [] + end + + cleaned = urls.map { |url| url.strip.gsub(%r{/*\z}, '') }.reject(&:blank?) write_attribute(:elasticsearch_url, cleaned.join(',')) end diff --git a/ee/spec/models/application_setting_spec.rb b/ee/spec/models/application_setting_spec.rb index 1b54705ca9721032d05c068980cd5befe0608f5d..26b1c300be19d919229df0d69a1a50d63bc87883 100644 --- a/ee/spec/models/application_setting_spec.rb +++ b/ee/spec/models/application_setting_spec.rb @@ -1699,32 +1699,36 @@ def expect_is_es_licensed end describe '#elasticsearch_url', feature_category: :global_search do - it 'presents a single URL as a one-element array' do - setting.elasticsearch_url = 'http://example.com' + using RSpec::Parameterized::TableSyntax + + # rubocop:disable Layout/LineLength -- Keep one-line table syntax in this case + where(:input_value, :expected_urls) do + 'http://example.com' | [URI.parse('http://example.com')] + 'http://example.com,https://invalid.invalid:9200' | [URI.parse('http://example.com'), URI.parse('https://invalid.invalid:9200')] + ' http://example.com, https://invalid.invalid:9200 ' | [URI.parse('http://example.com'), URI.parse('https://invalid.invalid:9200')] + 'http://example.com/, https://example.com:9200/, https://example.com:9200/prefix//' | [URI.parse('http://example.com'), URI.parse('https://example.com:9200'), URI.parse('https://example.com:9200/prefix')] + + # Array inputs + ['http://example.com'] | [URI.parse('http://example.com')] + ['http://example.com', 'https://other.example.com:9200'] | [URI.parse('http://example.com'), URI.parse('https://other.example.com:9200')] + ['http://example.com, https://other.example.com:9200'] | [URI.parse('http://example.com'), URI.parse('https://other.example.com:9200')] + [' http://example.com ', ' https://other.example.com:9200 '] | [URI.parse('http://example.com'), URI.parse('https://other.example.com:9200')] + + # Edge cases + [] | [] + [URI.parse('http://example.com')] | [URI.parse('http://example.com')] + '' | [] + nil | [] + 123 | [] + end + # rubocop:enable Layout/LineLength - expect(setting.elasticsearch_url).to match_array([URI.parse('http://example.com')]) - end - - it 'presents multiple URLs as a many-element array' do - setting.elasticsearch_url = 'http://example.com,https://invalid.invalid:9200' - - expect(setting.elasticsearch_url).to match_array([URI.parse('http://example.com'), URI.parse('https://invalid.invalid:9200')]) - end - - it 'strips whitespace from around URLs' do - setting.elasticsearch_url = ' http://example.com, https://invalid.invalid:9200 ' - - expect(setting.elasticsearch_url).to match_array([URI.parse('http://example.com'), URI.parse('https://invalid.invalid:9200')]) - end - - it 'strips trailing slashes from URLs' do - setting.elasticsearch_url = 'http://example.com/, https://example.com:9200/, https://example.com:9200/prefix//' + with_them do + it 'correctly parses the array input' do + setting.elasticsearch_url = input_value - expect(setting.elasticsearch_url).to match_array([ - URI.parse('http://example.com'), - URI.parse('https://example.com:9200'), - URI.parse('https://example.com:9200/prefix') - ]) + expect(setting.elasticsearch_url).to match_array(expected_urls) + end end end