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 +4 -4
- data/CHANGELOG.md +147 -5
- data/lib/sumologic/cli/commands/base_command.rb +51 -0
- data/lib/sumologic/cli/commands/list_collectors_command.rb +22 -0
- data/lib/sumologic/cli/commands/list_sources_command.rb +53 -0
- data/lib/sumologic/cli/commands/search_command.rb +88 -0
- data/lib/sumologic/cli.rb +12 -149
- data/lib/sumologic/http/client.rb +27 -50
- data/lib/sumologic/http/connection_pool.rb +14 -0
- data/lib/sumologic/http/cookie_jar.rb +52 -0
- data/lib/sumologic/http/debug_logger.rb +45 -0
- data/lib/sumologic/http/request_builder.rb +63 -0
- data/lib/sumologic/http/response_handler.rb +42 -0
- data/lib/sumologic/version.rb +1 -1
- metadata +10 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: f674ac09efecc2c167df4d3853be9d9bd5c1c043bf6e0d574f068770e9979a8b
|
|
4
|
+
data.tar.gz: 4e43daf71638156ad4ac9f72b862daf501f4714037b3cee603aac1a6e8de1efb
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: cc1c1a801d4c0dcf282ae281cc4da00eb125095b036f45489cda7c74638e4116fde5ad5c317896fb8e05988fd143887d7b470f31d02fba10426cc3c3c7bd78a0
|
|
7
|
+
data.tar.gz: 3b360acf874598ea9ca2513d1ddd507c617e970b94f79efb5029fc3a2e37e807b1f8aac2e3e9cbe95793ef0d74f210fd2ab13e9d7d3f0d25fad446014f6f46cb
|
data/CHANGELOG.md
CHANGED
|
@@ -1,28 +1,170 @@
|
|
|
1
|
-
|
|
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
|
-
|
|
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
|
-
|
|
78
|
+
- Update CLI and documentation to support new interactive feature
|
|
79
|
+
- Add usage examples for interactive mode
|
|
16
80
|
|
|
17
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
#
|
|
11
|
-
#
|
|
12
|
-
#
|
|
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
|
-
@
|
|
16
|
-
@
|
|
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
|
-
|
|
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
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
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
|
data/lib/sumologic/version.rb
CHANGED
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.
|
|
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-
|
|
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
|