diff --git a/lib/sumologic/cli.rb b/lib/sumologic/cli.rb index 4fb358b..a28c2a2 100644 --- a/lib/sumologic/cli.rb +++ b/lib/sumologic/cli.rb @@ -89,13 +89,17 @@ def search Commands::SearchCommand.new(options, create_client).execute end - desc 'list-collectors', 'List all Sumo Logic collectors' + desc 'list-collectors', 'List Sumo Logic collectors with optional filters' long_desc <<~DESC - List all collectors in your Sumo Logic account. + List collectors in your Sumo Logic account. + Supports filtering by name/category and limiting results. - Example: + Examples: + sumo-query list-collectors -q "my-service" -l 20 sumo-query list-collectors --output collectors.json DESC + option :query, type: :string, aliases: '-q', desc: 'Filter by name or category (case-insensitive)' + option :limit, type: :numeric, aliases: '-l', desc: 'Maximum collectors to return' def list_collectors Commands::ListCollectorsCommand.new(options, create_client).execute end diff --git a/lib/sumologic/cli/commands/list_collectors_command.rb b/lib/sumologic/cli/commands/list_collectors_command.rb index 1f11607..5869abd 100644 --- a/lib/sumologic/cli/commands/list_collectors_command.rb +++ b/lib/sumologic/cli/commands/list_collectors_command.rb @@ -8,7 +8,12 @@ module Commands # Handles the list-collectors command execution class ListCollectorsCommand < BaseCommand def execute - list_resource(label: 'collectors', key: :collectors) { client.list_collectors } + list_resource(label: 'collectors', key: :collectors) do + client.list_collectors( + query: options[:query], + limit: options[:limit] + ) + end end end end diff --git a/lib/sumologic/client.rb b/lib/sumologic/client.rb index 60f1dcc..e02e358 100644 --- a/lib/sumologic/client.rb +++ b/lib/sumologic/client.rb @@ -83,11 +83,13 @@ def search_aggregation(query:, from_time:, to_time:, time_zone: 'UTC', limit: ni ) end - # List all collectors + # List collectors with optional filtering # + # @param query [String, nil] Filter by name or category (case-insensitive) + # @param limit [Integer, nil] Maximum number of collectors to return # @return [Array] Array of collector hashes - def list_collectors - @collector.list + def list_collectors(query: nil, limit: nil) + @collector.list(query: query, limit: limit) end # List sources for a specific collector diff --git a/lib/sumologic/metadata/collector.rb b/lib/sumologic/metadata/collector.rb index 42ae148..8295e3c 100644 --- a/lib/sumologic/metadata/collector.rb +++ b/lib/sumologic/metadata/collector.rb @@ -13,9 +13,12 @@ def initialize(http_client:) @http = http_client end - # List all collectors - # Returns array of collector objects - def list + # List collectors with optional client-side filtering + # + # @param query [String, nil] Filter by name or category (case-insensitive substring match) + # @param limit [Integer, nil] Maximum number of collectors to return + # @return [Array] Array of collector objects + def list(query: nil, limit: nil) data = @http.request( method: :get, path: '/collectors' @@ -23,10 +26,25 @@ def list collectors = data['collectors'] || [] log_info "Found #{collectors.size} collectors" + + collectors = filter_by_query(collectors, query) if query + collectors = collectors.take(limit) if limit + collectors rescue StandardError => e raise Error, "Failed to list collectors: #{e.message}" end + + private + + def filter_by_query(collectors, query) + pattern = query.downcase + collectors.select do |c| + name = (c['name'] || '').downcase + category = (c['category'] || '').downcase + name.include?(pattern) || category.include?(pattern) + end + end end end end diff --git a/spec/sumologic/cli/commands/commands_spec.rb b/spec/sumologic/cli/commands/commands_spec.rb index ec1cd63..85bfcbc 100644 --- a/spec/sumologic/cli/commands/commands_spec.rb +++ b/spec/sumologic/cli/commands/commands_spec.rb @@ -24,11 +24,22 @@ def capture_stdout_stderr(command) describe Sumologic::CLI::Commands::ListCollectorsCommand do it 'outputs collectors as JSON' do - allow(client).to receive(:list_collectors).and_return([{ 'id' => '1', 'name' => 'c1' }]) + allow(client).to receive(:list_collectors) + .with(query: nil, limit: nil) + .and_return([{ 'id' => '1', 'name' => 'c1' }]) command = described_class.new(options, client) expect { command.execute }.to output(/"total": 1/).to_stdout end + + it 'passes query and limit filters' do + allow(client).to receive(:list_collectors) + .with(query: 'prod', limit: 10) + .and_return([{ 'id' => '1', 'name' => 'prod-web' }]) + + command = described_class.new(options.merge(query: 'prod', limit: 10), client) + expect { command.execute }.to output(/"total": 1/).to_stdout + end end describe Sumologic::CLI::Commands::ListAppsCommand do diff --git a/spec/sumologic/metadata/collector_spec.rb b/spec/sumologic/metadata/collector_spec.rb new file mode 100644 index 0000000..7d949ad --- /dev/null +++ b/spec/sumologic/metadata/collector_spec.rb @@ -0,0 +1,67 @@ +# frozen_string_literal: true + +RSpec.describe Sumologic::Metadata::Collector do + let(:http_client) { instance_double('Sumologic::Http::Client') } + let(:collector) { described_class.new(http_client: http_client) } + + let(:collectors_response) do + { + 'collectors' => [ + { 'id' => '1', 'name' => 'prod-web-01', 'category' => 'production/web' }, + { 'id' => '2', 'name' => 'prod-api-01', 'category' => 'production/api' }, + { 'id' => '3', 'name' => 'staging-web-01', 'category' => 'staging/web' }, + { 'id' => '4', 'name' => 'dev-local', 'category' => '' } + ] + } + end + + before do + allow(http_client).to receive(:request) + .with(method: :get, path: '/collectors') + .and_return(collectors_response) + end + + describe '#list' do + it 'returns all collectors when no filters given' do + result = collector.list + expect(result.size).to eq(4) + end + + it 'filters by name with query' do + result = collector.list(query: 'web') + expect(result.map { |c| c['id'] }).to eq(%w[1 3]) + end + + it 'filters by category with query' do + result = collector.list(query: 'staging') + expect(result.map { |c| c['id'] }).to eq(%w[3]) + end + + it 'is case-insensitive' do + result = collector.list(query: 'PROD') + expect(result.map { |c| c['id'] }).to eq(%w[1 2]) + end + + it 'limits results' do + result = collector.list(limit: 2) + expect(result.size).to eq(2) + end + + it 'applies query before limit' do + result = collector.list(query: 'prod', limit: 1) + expect(result.size).to eq(1) + expect(result.first['name']).to eq('prod-web-01') + end + + it 'handles empty response' do + allow(http_client).to receive(:request).and_return({ 'collectors' => [] }) + result = collector.list(query: 'anything') + expect(result).to eq([]) + end + + it 'raises Error on failure' do + allow(http_client).to receive(:request).and_raise(StandardError, 'network error') + expect { collector.list }.to raise_error(Sumologic::Error, /Failed to list collectors/) + end + end +end