sumologic-query 1.3.2 โ†’ 1.3.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: fff9b3ee00ddf6f3adfd2bb84fb8488068149fc8e7bfd94a3aa1b9854f7b74f2
4
- data.tar.gz: 898d75c6c4ca00c9e78ef21301d1345ec4e2c3806f33a8554b485486f314214a
3
+ metadata.gz: f674ac09efecc2c167df4d3853be9d9bd5c1c043bf6e0d574f068770e9979a8b
4
+ data.tar.gz: 4e43daf71638156ad4ac9f72b862daf501f4714037b3cee603aac1a6e8de1efb
5
5
  SHA512:
6
- metadata.gz: b9d264177ec993228a116d3c17f77cf1ac0b0e16c48d19a3de2b6b6502fcf9d89bfa90dc1046609f5819c18bcccb0a30eeb3c57b7ba00363a94f4e08f2705318
7
- data.tar.gz: 252af2f57e025938cbe9607eb5ae32b23140d0e84bbc8c8ecaceb10c06110436e37a096c9c6d21480c2408cbfb3f29a5f322e71655622954c5c7fc5881ac959f
6
+ metadata.gz: cc1c1a801d4c0dcf282ae281cc4da00eb125095b036f45489cda7c74638e4116fde5ad5c317896fb8e05988fd143887d7b470f31d02fba10426cc3c3c7bd78a0
7
+ data.tar.gz: 3b360acf874598ea9ca2513d1ddd507c617e970b94f79efb5029fc3a2e37e807b1f8aac2e3e9cbe95793ef0d74f210fd2ab13e9d7d3f0d25fad446014f6f46cb
data/CHANGELOG.md CHANGED
@@ -1,28 +1,170 @@
1
- ## [1.2.2](https://github.com/patrick204nqh/sumologic-query/compare/v1.2.1...v1.2.2) (2025-11-15)
1
+ # Changelog
2
2
 
3
+ All notable changes to this project will be documented in this file.
3
4
 
5
+ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
+
7
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
8
+ and release notes are automatically generated from commit messages.
9
+ ## [1.3.3](https://github.com/patrick204nqh/sumologic-query/compare/v1.3.2...v1.3.3) (2025-11-17)
10
+
11
+ ### ๐ŸŽ‰ New Features
12
+
13
+ - implement modular HTTP client components for improved organization and functionality
14
+ - refactor CLI structure to modular commands and remove deprecated modules
15
+ - update CHANGELOG entry creation to include changelog content directly
16
+ - bump version to 1.3.3
17
+ - add ADR for SSL certificate verification to address connection issues with Sumo Logic API
18
+ - refactor CLI structure into modular components for improved organization and maintainability
19
+ - implement debug logging for HTTP requests and responses
20
+ - enhance release notes generation and update CHANGELOG format for better clarity
4
21
 
5
- ## [1.2.1](https://github.com/patrick204nqh/sumologic-query/compare/v1.2.0...v1.2.1) (2025-11-14)
6
22
 
7
23
 
8
24
 
9
25
  ## [1.3.2](https://github.com/patrick204nqh/sumologic-query/compare/v1.3.1...v1.3.2) (2025-11-16)
10
26
 
11
- <!-- Release notes generated using configuration in .github/release.yml at main -->
27
+ ### ๐ŸŽ‰ New Features
28
+
29
+ - Refactor FzfViewer with modular configuration, formatting, and header building for better maintainability
30
+ - Add modular file structure with separate concerns (Config, Formatter, SearchableBuilder, FzfConfig, HeaderBuilder)
31
+ - Implement module_function for better encapsulation in all FzfViewer modules
32
+ - Add RubyGems download badge to README
33
+
34
+ ### ๐Ÿ› Bug Fixes
35
+
36
+ - Update source field reference in FzfViewer for consistency (use lowercase `_source` field)
37
+ - Fix RuboCop offenses across all FzfViewer modules
38
+
39
+ ### ๐Ÿ”ง Refactoring
40
+
41
+ - Extract searchable builder methods to reduce complexity
42
+ - Separate FzfViewer into 6 focused modules (~50-100 lines each)
43
+ - Use constants for display configuration (widths, colors, padding)
44
+ - Improve code organization with clear section headers
45
+
46
+ ### ๐Ÿงน Maintenance
47
+
48
+ - Update release notes generation and changelog management in CI pipeline
49
+
50
+ ## [1.3.1](https://github.com/patrick204nqh/sumologic-query/compare/v1.3.0...v1.3.1) (2025-11-15)
51
+
52
+ ### ๐Ÿงน Maintenance
53
+
54
+ - Automated version bump and release preparation
55
+
56
+ ## [1.3.0](https://github.com/patrick204nqh/sumologic-query/compare/v1.2.1...v1.3.0) (2025-11-15)
57
+
58
+ ### ๐Ÿ“š Documentation
59
+
60
+ - Establish commit message convention using Conventional Commits
61
+ - Update CONTRIBUTING.md for clarity and best practices
62
+
63
+ ### ๐Ÿงน Maintenance
64
+
65
+ - Remove path restriction for version file in release workflow
66
+ - Improve CI/CD pipeline configuration
67
+
68
+ ## [1.2.1](https://github.com/patrick204nqh/sumologic-query/compare/v1.2.0...v1.2.1) (2025-11-14)
69
+
70
+ ### ๐ŸŽ‰ New Features
12
71
 
72
+ - Add interactive mode with FZF for enhanced log exploration
73
+ - Support real-time log browsing and filtering
74
+ - Add keyboard shortcuts for common operations
13
75
 
76
+ ### ๐Ÿ“š Documentation
14
77
 
15
- **Full Changelog**: https://github.com/patrick204nqh/sumologic-query/compare/v1.3.1...v1.3.2
78
+ - Update CLI and documentation to support new interactive feature
79
+ - Add usage examples for interactive mode
16
80
 
17
- # [1.2.0](https://github.com/patrick204nqh/sumologic-query/compare/v1.1.2...v1.2.0) (2025-11-14)
81
+ ## [1.2.0](https://github.com/patrick204nqh/sumologic-query/compare/v1.1.2...v1.2.0) (2025-11-14)
18
82
 
83
+ ### ๐ŸŽ‰ New Features
19
84
 
85
+ - Add ADR 004 for enhanced progress tracking and user experience
86
+ - Implement real-time visibility with callbacks in CLI and fetcher classes
87
+ - Add comprehensive progress indicators for long-running operations
88
+
89
+ ### ๐Ÿ”ง Refactoring
90
+
91
+ - Implement reusable Worker utility for parallel execution
92
+ - Refactor metadata and search fetching classes to utilize Worker
93
+ - Remove deprecated ParallelFetcher and Paginator
94
+ - Simplify pagination logic
95
+ - Remove streaming APIs for better maintainability
96
+ - Update configuration defaults for optimal performance
97
+
98
+ ### ๐Ÿ“š Documentation
99
+
100
+ - Reorganize architecture documentation files
101
+ - Add architectural overview and decision records
20
102
 
21
103
  ## [1.1.2](https://github.com/patrick204nqh/sumologic-query/compare/v1.1.1...v1.1.2) (2025-11-14)
22
104
 
105
+ ### ๐Ÿ› Bug Fixes
106
+
107
+ - Fix command syntax in tldr.md for listing collectors and sources
108
+
109
+ ### ๐ŸŽ‰ New Features
23
110
 
111
+ - Add version command to CLI
112
+
113
+ ### ๐Ÿ“š Documentation
114
+
115
+ - Add quick reference documentation (tldr.md)
116
+ - Refactor documentation structure
117
+ - Remove examples.md and consolidate content
118
+ - Streamline troubleshooting.md
119
+ - Consolidate queries.md with improved examples
24
120
 
25
121
  ## [1.1.1](https://github.com/patrick204nqh/sumologic-query/compare/v1.1.0...v1.1.1) (2025-11-14)
26
122
 
123
+ ### ๐Ÿ”ง Refactoring
124
+
125
+ - Refactor search logging in CLI and Poller classes for improved readability
126
+ - Consolidate attribute accessors in Configuration class
127
+ - Improve CLI options structure
128
+
129
+ ## [1.1.0](https://github.com/patrick204nqh/sumologic-query/compare/v1.0.1...v1.1.0) (2025-11-13)
130
+
131
+ ### ๐ŸŽ‰ New Features
132
+
133
+ - Add CLI support with Thor framework
134
+ - Refactor Sumo Logic client for better usability
135
+
136
+ ### ๐Ÿ”ง Refactoring
137
+
138
+ - Refine polling logic in Sumo Logic client
139
+ - Improve overall code structure and organization
140
+
141
+ ### ๐Ÿงน Maintenance
142
+
143
+ - Refactor CI and release workflows
144
+ - Streamline version checking and build process
145
+ - Improve release tagging automation
146
+
147
+ ## [1.0.1](https://github.com/patrick204nqh/sumologic-query/compare/v1.0.0...v1.0.1) (2025-11-13)
148
+
149
+ ### ๐ŸŽ‰ New Features
150
+
151
+ - Add CODEOWNERS file for repository management
152
+
153
+ ### ๐Ÿงน Maintenance
154
+
155
+ - Refactor release workflow to generate release notes using GitHub API
156
+ - Update CHANGELOG.md format for better clarity
157
+ - Add changelog extraction for automated versioning
158
+
159
+ ## [1.0.0](https://github.com/patrick204nqh/sumologic-query/releases/tag/v1.0.0) (2025-11-13)
27
160
 
161
+ ### ๐ŸŽ‰ Initial Release
28
162
 
163
+ - Initial release of Sumo Logic Query Tool
164
+ - Core search functionality
165
+ - Metadata querying (collectors, sources)
166
+ - Basic CLI interface
167
+ - HTTP client with authentication
168
+ - Automated pagination
169
+ - Search job polling
170
+ - JSON output support
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sumologic
4
+ class CLI < Thor
5
+ module Commands
6
+ # Base class for all CLI commands
7
+ # Provides common functionality like client creation, output handling, and formatting
8
+ class BaseCommand
9
+ attr_reader :options, :client
10
+
11
+ def initialize(options, client)
12
+ @options = options
13
+ @client = client
14
+ end
15
+
16
+ private
17
+
18
+ def output_json(data)
19
+ json_output = JSON.pretty_generate(data)
20
+
21
+ if options[:output]
22
+ File.write(options[:output], json_output)
23
+ warn "\nResults saved to: #{options[:output]}"
24
+ else
25
+ puts json_output
26
+ end
27
+ end
28
+
29
+ def format_collector(collector)
30
+ {
31
+ id: collector['id'],
32
+ name: collector['name'],
33
+ collectorType: collector['collectorType'],
34
+ alive: collector['alive'],
35
+ category: collector['category']
36
+ }
37
+ end
38
+
39
+ def format_source(source)
40
+ {
41
+ id: source['id'],
42
+ name: source['name'],
43
+ category: source['category'],
44
+ sourceType: source['sourceType'],
45
+ alive: source['alive']
46
+ }
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'base_command'
4
+
5
+ module Sumologic
6
+ class CLI < Thor
7
+ module Commands
8
+ # Handles the list-collectors command execution
9
+ class ListCollectorsCommand < BaseCommand
10
+ def execute
11
+ warn 'Fetching collectors...'
12
+ collectors = client.list_collectors
13
+
14
+ output_json(
15
+ total: collectors.size,
16
+ collectors: collectors.map { |c| format_collector(c) }
17
+ )
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'base_command'
4
+
5
+ module Sumologic
6
+ class CLI < Thor
7
+ module Commands
8
+ # Handles the list-sources command execution
9
+ class ListSourcesCommand < BaseCommand
10
+ def execute
11
+ if options[:collector_id]
12
+ list_sources_for_collector
13
+ else
14
+ list_all_sources
15
+ end
16
+ end
17
+
18
+ private
19
+
20
+ def list_sources_for_collector
21
+ warn "Fetching sources for collector: #{options[:collector_id]}"
22
+ sources = client.list_sources(collector_id: options[:collector_id])
23
+
24
+ output_json(
25
+ collector_id: options[:collector_id],
26
+ total: sources.size,
27
+ sources: sources.map { |s| format_source(s) }
28
+ )
29
+ end
30
+
31
+ def list_all_sources
32
+ warn 'Fetching all sources from all collectors...'
33
+ warn 'This may take a minute...'
34
+
35
+ all_sources = client.list_all_sources
36
+
37
+ output_json(
38
+ total_collectors: all_sources.size,
39
+ total_sources: all_sources.sum { |c| c['sources'].size },
40
+ data: all_sources.map { |item| format_collector_with_sources(item) }
41
+ )
42
+ end
43
+
44
+ def format_collector_with_sources(item)
45
+ {
46
+ collector: item['collector'],
47
+ sources: item['sources'].map { |s| format_source(s) }
48
+ }
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,88 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'base_command'
4
+
5
+ module Sumologic
6
+ class CLI < Thor
7
+ module Commands
8
+ # Handles the search command execution
9
+ class SearchCommand < BaseCommand
10
+ def execute
11
+ log_search_info
12
+ results = perform_search
13
+
14
+ display_results_summary(results)
15
+
16
+ if options[:interactive]
17
+ launch_interactive_mode(results)
18
+ else
19
+ output_search_results(results)
20
+ end
21
+ end
22
+
23
+ private
24
+
25
+ def log_search_info
26
+ warn '=' * 60
27
+ warn 'Sumo Logic Search Query'
28
+ warn '=' * 60
29
+ warn "Time Range: #{options[:from]} to #{options[:to]}"
30
+ warn "Query: #{options[:query]}"
31
+ warn "Limit: #{options[:limit] || 'unlimited'}"
32
+ warn '-' * 60
33
+ warn 'Creating search job...'
34
+ $stderr.puts
35
+ end
36
+
37
+ def perform_search
38
+ client.search(
39
+ query: options[:query],
40
+ from_time: options[:from],
41
+ to_time: options[:to],
42
+ time_zone: options[:time_zone],
43
+ limit: options[:limit]
44
+ )
45
+ end
46
+
47
+ def display_results_summary(results)
48
+ warn '=' * 60
49
+ warn "Results: #{results.size} messages"
50
+ warn '=' * 60
51
+ $stderr.puts
52
+ end
53
+
54
+ def output_search_results(results)
55
+ output_json(
56
+ query: options[:query],
57
+ from: options[:from],
58
+ to: options[:to],
59
+ time_zone: options[:time_zone],
60
+ message_count: results.size,
61
+ messages: results
62
+ )
63
+ end
64
+
65
+ def launch_interactive_mode(results)
66
+ require_relative '../../interactive'
67
+
68
+ formatted_results = build_formatted_results(results)
69
+ Sumologic::Interactive.launch(formatted_results)
70
+ rescue Sumologic::Interactive::Error => e
71
+ warn e.message
72
+ exit 1
73
+ end
74
+
75
+ def build_formatted_results(results)
76
+ {
77
+ 'query' => options[:query],
78
+ 'from' => options[:from],
79
+ 'to' => options[:to],
80
+ 'time_zone' => options[:time_zone],
81
+ 'message_count' => results.size,
82
+ 'messages' => results
83
+ }
84
+ end
85
+ end
86
+ end
87
+ end
88
+ end
data/lib/sumologic/cli.rb CHANGED
@@ -2,9 +2,13 @@
2
2
 
3
3
  require 'thor'
4
4
  require 'json'
5
+ require_relative 'cli/commands/search_command'
6
+ require_relative 'cli/commands/list_collectors_command'
7
+ require_relative 'cli/commands/list_sources_command'
5
8
 
6
9
  module Sumologic
7
10
  # Thor-based CLI for Sumo Logic query tool
11
+ # Delegates commands to specialized command classes
8
12
  class CLI < Thor
9
13
  class_option :debug, type: :boolean, aliases: '-d', desc: 'Enable debug output'
10
14
  class_option :output, type: :string, aliases: '-o', desc: 'Output file (default: stdout)'
@@ -13,6 +17,11 @@ module Sumologic
13
17
  true
14
18
  end
15
19
 
20
+ def initialize(*args)
21
+ super
22
+ $DEBUG = true if options[:debug]
23
+ end
24
+
16
25
  desc 'search', 'Search Sumo Logic logs'
17
26
  long_desc <<~DESC
18
27
  Search Sumo Logic logs using a query string.
@@ -39,23 +48,7 @@ module Sumologic
39
48
  option :limit, type: :numeric, aliases: '-l', desc: 'Maximum messages to return'
40
49
  option :interactive, type: :boolean, aliases: '-i', desc: 'Launch interactive browser (requires fzf)'
41
50
  def search
42
- $DEBUG = true if options[:debug]
43
-
44
- client = create_client
45
-
46
- log_search_info
47
- results = execute_search(client)
48
-
49
- warn '=' * 60
50
- warn "Results: #{results.size} messages"
51
- warn '=' * 60
52
- $stderr.puts
53
-
54
- if options[:interactive]
55
- launch_interactive_mode(results)
56
- else
57
- output_search_results(results)
58
- end
51
+ Commands::SearchCommand.new(options, create_client).execute
59
52
  end
60
53
 
61
54
  desc 'list-collectors', 'List all Sumo Logic collectors'
@@ -66,17 +59,7 @@ module Sumologic
66
59
  sumo-query list-collectors --output collectors.json
67
60
  DESC
68
61
  def list_collectors
69
- $DEBUG = true if options[:debug]
70
-
71
- client = create_client
72
-
73
- warn 'Fetching collectors...'
74
- collectors = client.list_collectors
75
-
76
- output_json(
77
- total: collectors.size,
78
- collectors: collectors.map { |c| format_collector(c) }
79
- )
62
+ Commands::ListCollectorsCommand.new(options, create_client).execute
80
63
  end
81
64
 
82
65
  desc 'list-sources', 'List sources from collectors'
@@ -92,15 +75,7 @@ module Sumologic
92
75
  DESC
93
76
  option :collector_id, type: :string, desc: 'Collector ID to list sources for'
94
77
  def list_sources
95
- $DEBUG = true if options[:debug]
96
-
97
- client = create_client
98
-
99
- if options[:collector_id]
100
- list_sources_for_collector(client, options[:collector_id])
101
- else
102
- list_all_sources(client)
103
- end
78
+ Commands::ListSourcesCommand.new(options, create_client).execute
104
79
  end
105
80
 
106
81
  desc 'version', 'Show version information'
@@ -127,120 +102,8 @@ module Sumologic
127
102
  exit 1
128
103
  end
129
104
 
130
- def list_sources_for_collector(client, collector_id)
131
- warn "Fetching sources for collector: #{collector_id}"
132
- sources = client.list_sources(collector_id: collector_id)
133
-
134
- output_json(
135
- collector_id: collector_id,
136
- total: sources.size,
137
- sources: sources.map { |s| format_source(s) }
138
- )
139
- end
140
-
141
- def list_all_sources(client)
142
- warn 'Fetching all sources from all collectors...'
143
- warn 'This may take a minute...'
144
-
145
- all_sources = client.list_all_sources
146
-
147
- output_json(
148
- total_collectors: all_sources.size,
149
- total_sources: all_sources.sum { |c| c['sources'].size },
150
- data: all_sources.map do |item|
151
- {
152
- collector: item['collector'],
153
- sources: item['sources'].map { |s| format_source(s) }
154
- }
155
- end
156
- )
157
- end
158
-
159
- def format_collector(collector)
160
- {
161
- id: collector['id'],
162
- name: collector['name'],
163
- collectorType: collector['collectorType'],
164
- alive: collector['alive'],
165
- category: collector['category']
166
- }
167
- end
168
-
169
- def format_source(source)
170
- {
171
- id: source['id'],
172
- name: source['name'],
173
- category: source['category'],
174
- sourceType: source['sourceType'],
175
- alive: source['alive']
176
- }
177
- end
178
-
179
- def output_json(data)
180
- json_output = JSON.pretty_generate(data)
181
-
182
- if options[:output]
183
- File.write(options[:output], json_output)
184
- warn "\nResults saved to: #{options[:output]}"
185
- else
186
- puts json_output
187
- end
188
- end
189
-
190
105
  def error(message)
191
106
  warn message
192
107
  end
193
-
194
- def log_search_info
195
- warn '=' * 60
196
- warn 'Sumo Logic Search Query'
197
- warn '=' * 60
198
- warn "Time Range: #{options[:from]} to #{options[:to]}"
199
- warn "Query: #{options[:query]}"
200
- warn "Limit: #{options[:limit] || 'unlimited'}"
201
- warn '-' * 60
202
- warn 'Creating search job...'
203
- $stderr.puts
204
- end
205
-
206
- def execute_search(client)
207
- client.search(
208
- query: options[:query],
209
- from_time: options[:from],
210
- to_time: options[:to],
211
- time_zone: options[:time_zone],
212
- limit: options[:limit]
213
- )
214
- end
215
-
216
- def output_search_results(results)
217
- output_json(
218
- query: options[:query],
219
- from: options[:from],
220
- to: options[:to],
221
- time_zone: options[:time_zone],
222
- message_count: results.size,
223
- messages: results
224
- )
225
- end
226
-
227
- def launch_interactive_mode(results)
228
- require_relative 'interactive'
229
-
230
- # Format results for interactive mode
231
- formatted_results = {
232
- 'query' => options[:query],
233
- 'from' => options[:from],
234
- 'to' => options[:to],
235
- 'time_zone' => options[:time_zone],
236
- 'message_count' => results.size,
237
- 'messages' => results
238
- }
239
-
240
- Sumologic::Interactive.launch(formatted_results)
241
- rescue Sumologic::Interactive::Error => e
242
- error e.message
243
- exit 1
244
- end
245
108
  end
246
109
  end
@@ -1,30 +1,41 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'net/http'
4
- require 'json'
5
- require 'uri'
6
3
  require_relative 'connection_pool'
4
+ require_relative 'debug_logger'
5
+ require_relative 'cookie_jar'
6
+ require_relative 'request_builder'
7
+ require_relative 'response_handler'
7
8
 
8
9
  module Sumologic
9
10
  module Http
10
- # Handles HTTP communication with Sumo Logic API
11
- # Responsibilities: request execution, error handling, SSL configuration
12
- # Uses connection pooling for thread-safe parallel requests
11
+ # Orchestrates HTTP communication with Sumo Logic API
12
+ # Delegates to specialized components for request building,
13
+ # response handling, connection pooling, and cookie management
13
14
  class Client
14
15
  def initialize(base_url:, authenticator:)
15
- @base_url = base_url
16
- @authenticator = authenticator
16
+ @cookie_jar = CookieJar.new
17
+ @request_builder = RequestBuilder.new(
18
+ base_url: base_url,
19
+ authenticator: authenticator,
20
+ cookie_jar: @cookie_jar
21
+ )
22
+ @response_handler = ResponseHandler.new
17
23
  @connection_pool = ConnectionPool.new(base_url: base_url, max_connections: 10)
18
24
  end
19
25
 
20
26
  # Execute HTTP request with error handling
21
27
  # Uses connection pool for thread-safe parallel execution
22
28
  def request(method:, path:, body: nil, query_params: nil)
23
- uri = build_uri(path, query_params)
24
- request = build_request(method, uri, body)
29
+ uri = @request_builder.build_uri(path, query_params)
30
+ request = @request_builder.build_request(method, uri, body)
31
+
32
+ DebugLogger.log_request(method, uri, body)
25
33
 
26
34
  response = execute_request(uri, request)
27
- handle_response(response)
35
+
36
+ DebugLogger.log_response(response)
37
+
38
+ @response_handler.handle(response)
28
39
  rescue Errno::ECONNRESET, Errno::EPIPE, EOFError, Net::HTTPBadResponse => e
29
40
  # Connection error - raise for retry at higher level
30
41
  raise Error, "Connection error: #{e.message}"
@@ -37,49 +48,15 @@ module Sumologic
37
48
 
38
49
  private
39
50
 
40
- def build_uri(path, query_params)
41
- uri = URI("#{@base_url}#{path}")
42
- uri.query = URI.encode_www_form(query_params) if query_params
43
- uri
44
- end
45
-
46
- def build_request(method, uri, body)
47
- request_class = case method
48
- when :get then Net::HTTP::Get
49
- when :post then Net::HTTP::Post
50
- when :delete then Net::HTTP::Delete
51
- else raise ArgumentError, "Unsupported HTTP method: #{method}"
52
- end
53
-
54
- request = request_class.new(uri)
55
- request['Authorization'] = @authenticator.auth_header
56
- request['Accept'] = 'application/json'
57
-
58
- if body
59
- request['Content-Type'] = 'application/json'
60
- request.body = body.to_json
61
- end
62
-
63
- request
64
- end
65
-
66
51
  def execute_request(uri, request)
67
- @connection_pool.with_connection(uri) do |http|
52
+ response = @connection_pool.with_connection(uri) do |http|
68
53
  http.request(request)
69
54
  end
70
- end
71
55
 
72
- def handle_response(response)
73
- case response.code.to_i
74
- when 200..299
75
- JSON.parse(response.body)
76
- when 401, 403
77
- raise AuthenticationError, "Authentication failed: #{response.body}"
78
- when 429
79
- raise Error, "Rate limit exceeded: #{response.body}"
80
- else
81
- raise Error, "HTTP #{response.code}: #{response.body}"
82
- end
56
+ # Store cookies from response for subsequent requests
57
+ @cookie_jar.store_from_response(response)
58
+
59
+ response
83
60
  end
84
61
  end
85
62
  end
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'openssl'
4
+
3
5
  module Sumologic
4
6
  module Http
5
7
  # Thread-safe connection pool for HTTP clients
@@ -84,10 +86,22 @@ module Sumologic
84
86
  http.read_timeout = READ_TIMEOUT
85
87
  http.open_timeout = OPEN_TIMEOUT
86
88
  http.keep_alive_timeout = 30
89
+
90
+ # SSL configuration
91
+ http.verify_mode = OpenSSL::SSL::VERIFY_PEER
92
+ http.cert_store = ssl_cert_store
93
+
87
94
  http.start
88
95
  http
89
96
  end
90
97
 
98
+ def ssl_cert_store
99
+ # Use system's default certificate store
100
+ store = OpenSSL::X509::Store.new
101
+ store.set_default_paths
102
+ store
103
+ end
104
+
91
105
  def create_temporary_connection(uri)
92
106
  # Fallback: create a temporary connection if pool is exhausted
93
107
  create_connection(uri)
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sumologic
4
+ module Http
5
+ # Simple cookie jar for storing and managing HTTP cookies
6
+ # Handles Set-Cookie response headers and Cookie request headers
7
+ class CookieJar
8
+ def initialize
9
+ @cookies = {}
10
+ end
11
+
12
+ # Store cookies from response Set-Cookie headers
13
+ def store_from_response(response)
14
+ return unless response['Set-Cookie']
15
+
16
+ Array(response['Set-Cookie']).each do |cookie_header|
17
+ parse_and_store(cookie_header)
18
+ end
19
+ end
20
+
21
+ # Format cookies for Cookie request header
22
+ # Returns nil if no cookies stored
23
+ def to_header
24
+ return nil if @cookies.empty?
25
+
26
+ @cookies.map { |name, value| "#{name}=#{value}" }.join('; ')
27
+ end
28
+
29
+ # Check if any cookies are stored
30
+ def any?
31
+ @cookies.any?
32
+ end
33
+
34
+ # Clear all stored cookies
35
+ def clear
36
+ @cookies.clear
37
+ end
38
+
39
+ private
40
+
41
+ def parse_and_store(cookie_header)
42
+ # Parse cookie name=value (ignore path, domain, expires, etc.)
43
+ # Example: "session_id=abc123; Path=/; HttpOnly"
44
+ return unless cookie_header =~ /^([^=]+)=([^;]+)/
45
+
46
+ name = Regexp.last_match(1).strip
47
+ value = Regexp.last_match(2).strip
48
+ @cookies[name] = value
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'json'
4
+
5
+ module Sumologic
6
+ module Http
7
+ # Handles debug logging for HTTP requests and responses
8
+ # Only logs when $DEBUG is enabled
9
+ module DebugLogger
10
+ module_function
11
+
12
+ def log_request(method, uri, body)
13
+ return unless $DEBUG
14
+
15
+ warn "\n[DEBUG] API Request:"
16
+ warn " Method: #{method.to_s.upcase}"
17
+ warn " URL: #{uri}"
18
+ log_request_body(body) if body
19
+ warn ''
20
+ end
21
+
22
+ def log_response(response)
23
+ return unless $DEBUG
24
+
25
+ warn '[DEBUG] API Response:'
26
+ warn " Status: #{response.code} #{response.message}"
27
+ log_response_body(response.body)
28
+ warn ''
29
+ end
30
+
31
+ def log_request_body(body)
32
+ warn " Body: #{JSON.pretty_generate(body)}"
33
+ rescue JSON::GeneratorError
34
+ warn " Body: #{body.inspect}"
35
+ end
36
+
37
+ def log_response_body(body)
38
+ truncated = body.length > 500
39
+ display_body = truncated ? "#{body[0..500]}..." : body
40
+ warn " Body: #{display_body}"
41
+ warn " (truncated, full length: #{body.length} characters)" if truncated
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,63 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'net/http'
4
+ require 'json'
5
+ require 'uri'
6
+
7
+ module Sumologic
8
+ module Http
9
+ # Builds HTTP requests with proper headers, authentication, and cookies
10
+ class RequestBuilder
11
+ def initialize(base_url:, authenticator:, cookie_jar:)
12
+ @base_url = base_url
13
+ @authenticator = authenticator
14
+ @cookie_jar = cookie_jar
15
+ end
16
+
17
+ # Build complete URI from path and query parameters
18
+ def build_uri(path, query_params = nil)
19
+ uri = URI("#{@base_url}#{path}")
20
+ uri.query = URI.encode_www_form(query_params) if query_params
21
+ uri
22
+ end
23
+
24
+ # Build HTTP request with all necessary headers
25
+ def build_request(method, uri, body = nil)
26
+ request = create_request_object(method, uri)
27
+ add_headers(request)
28
+ add_body(request, body) if body
29
+ request
30
+ end
31
+
32
+ private
33
+
34
+ def create_request_object(method, uri)
35
+ request_class = request_class_for(method)
36
+ request_class.new(uri)
37
+ end
38
+
39
+ def request_class_for(method)
40
+ case method
41
+ when :get then Net::HTTP::Get
42
+ when :post then Net::HTTP::Post
43
+ when :delete then Net::HTTP::Delete
44
+ else raise ArgumentError, "Unsupported HTTP method: #{method}"
45
+ end
46
+ end
47
+
48
+ def add_headers(request)
49
+ request['Authorization'] = @authenticator.auth_header
50
+ request['Accept'] = 'application/json'
51
+
52
+ # Add cookies if available
53
+ cookie_header = @cookie_jar.to_header
54
+ request['Cookie'] = cookie_header if cookie_header
55
+ end
56
+
57
+ def add_body(request, body)
58
+ request['Content-Type'] = 'application/json'
59
+ request.body = body.to_json
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'json'
4
+
5
+ module Sumologic
6
+ module Http
7
+ # Handles HTTP response parsing and error handling
8
+ class ResponseHandler
9
+ # Parse response and handle errors
10
+ def handle(response)
11
+ case response.code.to_i
12
+ when 200..299
13
+ parse_success(response)
14
+ when 401, 403
15
+ handle_authentication_error(response)
16
+ when 429
17
+ handle_rate_limit_error(response)
18
+ else
19
+ handle_generic_error(response)
20
+ end
21
+ end
22
+
23
+ private
24
+
25
+ def parse_success(response)
26
+ JSON.parse(response.body)
27
+ end
28
+
29
+ def handle_authentication_error(response)
30
+ raise AuthenticationError, "Authentication failed: #{response.body}"
31
+ end
32
+
33
+ def handle_rate_limit_error(response)
34
+ raise Error, "Rate limit exceeded: #{response.body}"
35
+ end
36
+
37
+ def handle_generic_error(response)
38
+ raise Error, "HTTP #{response.code}: #{response.body}"
39
+ end
40
+ end
41
+ end
42
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Sumologic
4
- VERSION = '1.3.2'
4
+ VERSION = '1.3.3'
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sumologic-query
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.3.2
4
+ version: 1.3.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - patrick204nqh
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2025-11-16 00:00:00.000000000 Z
11
+ date: 2025-11-17 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: base64
@@ -97,11 +97,19 @@ files:
97
97
  - bin/sumo-query
98
98
  - lib/sumologic.rb
99
99
  - lib/sumologic/cli.rb
100
+ - lib/sumologic/cli/commands/base_command.rb
101
+ - lib/sumologic/cli/commands/list_collectors_command.rb
102
+ - lib/sumologic/cli/commands/list_sources_command.rb
103
+ - lib/sumologic/cli/commands/search_command.rb
100
104
  - lib/sumologic/client.rb
101
105
  - lib/sumologic/configuration.rb
102
106
  - lib/sumologic/http/authenticator.rb
103
107
  - lib/sumologic/http/client.rb
104
108
  - lib/sumologic/http/connection_pool.rb
109
+ - lib/sumologic/http/cookie_jar.rb
110
+ - lib/sumologic/http/debug_logger.rb
111
+ - lib/sumologic/http/request_builder.rb
112
+ - lib/sumologic/http/response_handler.rb
105
113
  - lib/sumologic/interactive.rb
106
114
  - lib/sumologic/interactive/fzf_viewer.rb
107
115
  - lib/sumologic/interactive/fzf_viewer/config.rb