stackharbinger 0.3.0 → 0.4.0
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/README.md +39 -7
- data/docs/index.html +17 -13
- data/lib/harbinger/analyzers/database_detector.rb +13 -3
- data/lib/harbinger/analyzers/docker_compose_detector.rb +121 -0
- data/lib/harbinger/analyzers/mongo_detector.rb +104 -0
- data/lib/harbinger/analyzers/mysql_detector.rb +8 -1
- data/lib/harbinger/analyzers/postgres_detector.rb +6 -0
- data/lib/harbinger/analyzers/redis_detector.rb +98 -0
- data/lib/harbinger/analyzers/ruby_detector.rb +9 -1
- data/lib/harbinger/cli.rb +245 -54
- data/lib/harbinger/eol_fetcher.rb +19 -10
- data/lib/harbinger/exporters/base_exporter.rb +97 -0
- data/lib/harbinger/exporters/csv_exporter.rb +36 -0
- data/lib/harbinger/exporters/json_exporter.rb +21 -0
- data/lib/harbinger/version.rb +1 -1
- metadata +12 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 663f135c9d728ba59e2f33d33ddd73e3be1b37143c7533db0534db995301aa8e
|
|
4
|
+
data.tar.gz: de00d8ba69996d960b57bc96d49c79da0f9221a90ac870805fd9f5aad16b506f
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 2505777f55661fe67224a94351e8eb00d56cb2ef834c19a86f00e9be6fd45cf4aea5abb872242750c5f4dc3608e4707e0ecb8899538407d348ff7fdec7254a3c
|
|
7
|
+
data.tar.gz: 1ee3dcbca6a3cd75ede6a720fdb50bc8969a070c4bbb0c8a17cb4abacc0b9cb4536dd78fe0b78cb7609d56e15fc5affdcebac1ec5a906911cd3e323341b882bf
|
data/README.md
CHANGED
|
@@ -86,6 +86,31 @@ PostgreSQL 16.11:
|
|
|
86
86
|
```bash
|
|
87
87
|
# Show dashboard of all tracked projects
|
|
88
88
|
harbinger show
|
|
89
|
+
|
|
90
|
+
# Filter to specific project(s) by name or path
|
|
91
|
+
harbinger show budget
|
|
92
|
+
harbinger show job
|
|
93
|
+
|
|
94
|
+
# Show project paths with verbose mode
|
|
95
|
+
harbinger show -v
|
|
96
|
+
harbinger show job --verbose
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
### Export data
|
|
100
|
+
|
|
101
|
+
```bash
|
|
102
|
+
# Export to JSON (stdout)
|
|
103
|
+
harbinger show --format json
|
|
104
|
+
|
|
105
|
+
# Export to CSV (stdout)
|
|
106
|
+
harbinger show --format csv
|
|
107
|
+
|
|
108
|
+
# Save to file
|
|
109
|
+
harbinger show --format json -o report.json
|
|
110
|
+
harbinger show --format csv --output eol-report.csv
|
|
111
|
+
|
|
112
|
+
# Export filtered projects
|
|
113
|
+
harbinger show myproject --format json
|
|
89
114
|
```
|
|
90
115
|
|
|
91
116
|
**Example output:**
|
|
@@ -113,6 +138,13 @@ harbinger rescan
|
|
|
113
138
|
harbinger rescan --verbose
|
|
114
139
|
```
|
|
115
140
|
|
|
141
|
+
### Remove a project
|
|
142
|
+
|
|
143
|
+
```bash
|
|
144
|
+
# Remove a project from tracking
|
|
145
|
+
harbinger remove my-project
|
|
146
|
+
```
|
|
147
|
+
|
|
116
148
|
### Update EOL data
|
|
117
149
|
|
|
118
150
|
```bash
|
|
@@ -197,19 +229,19 @@ bundle exec exe/harbinger scan .
|
|
|
197
229
|
|
|
198
230
|
## Roadmap
|
|
199
231
|
|
|
200
|
-
### V0.
|
|
232
|
+
### V0.4.0 - Current
|
|
233
|
+
- ✅ Export reports to JSON/CSV
|
|
234
|
+
- ✅ Docker Compose database version detection
|
|
235
|
+
- ✅ Redis version detection
|
|
236
|
+
- ✅ MongoDB version detection
|
|
237
|
+
|
|
238
|
+
### V0.3.0
|
|
201
239
|
- ✅ PostgreSQL version detection with local/remote database handling
|
|
202
240
|
- ✅ MySQL version detection (mysql2 and trilogy adapters)
|
|
203
241
|
- ✅ Rescan command to update all tracked projects
|
|
204
242
|
- ✅ Enhanced dashboard with database columns
|
|
205
243
|
- ✅ EOL tracking for PostgreSQL and MySQL
|
|
206
244
|
|
|
207
|
-
### V0.4.0 - Planned
|
|
208
|
-
- 📋 Export reports to JSON/CSV
|
|
209
|
-
- 🐳 Docker Compose database version detection
|
|
210
|
-
- 🔴 Redis version detection
|
|
211
|
-
- 🍃 MongoDB version detection
|
|
212
|
-
|
|
213
245
|
### V1.0 - Future
|
|
214
246
|
- 🐍 Python support (pyproject.toml, requirements.txt)
|
|
215
247
|
- 📦 Node.js support (package.json, .nvmrc)
|
data/docs/index.html
CHANGED
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
<div class="max-w-6xl mx-auto px-4 py-20">
|
|
18
18
|
<h1 class="text-5xl md:text-7xl font-black mb-6">Harbinger</h1>
|
|
19
19
|
<p class="text-2xl md:text-3xl mb-8 text-blue-100">Track End-of-Life dates for your tech stack</p>
|
|
20
|
-
<p class="text-xl mb-12 text-blue-100 max-w-2xl">Never get caught off-guard by unsupported dependencies. Harbinger scans your Ruby and
|
|
20
|
+
<p class="text-xl mb-12 text-blue-100 max-w-2xl">Never get caught off-guard by unsupported dependencies. Harbinger scans your Ruby, Rails, PostgreSQL, and MySQL versions and warns you before support ends.</p>
|
|
21
21
|
|
|
22
22
|
<div class="flex flex-col sm:flex-row gap-4">
|
|
23
23
|
<div class="bg-gray-900 rounded-lg p-4 font-mono text-sm">
|
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
<div class="bg-white p-6 rounded-lg shadow-sm">
|
|
39
39
|
<div class="text-4xl mb-4">🔍</div>
|
|
40
40
|
<h3 class="text-xl font-semibold mb-2">Auto-Detection</h3>
|
|
41
|
-
<p class="text-gray-600">
|
|
41
|
+
<p class="text-gray-600">Detects Ruby, Rails, PostgreSQL, and MySQL versions from project files and database configs</p>
|
|
42
42
|
</div>
|
|
43
43
|
<div class="bg-white p-6 rounded-lg shadow-sm">
|
|
44
44
|
<div class="text-4xl mb-4">📅</div>
|
|
@@ -62,8 +62,8 @@
|
|
|
62
62
|
</div>
|
|
63
63
|
<div class="bg-white p-6 rounded-lg shadow-sm">
|
|
64
64
|
<div class="text-4xl mb-4">📊</div>
|
|
65
|
-
<h3 class="text-xl font-semibold mb-2">Dashboard
|
|
66
|
-
<p class="text-gray-600">
|
|
65
|
+
<h3 class="text-xl font-semibold mb-2">Smart Dashboard</h3>
|
|
66
|
+
<p class="text-gray-600">Dynamic columns show only relevant data - columns and projects without matches are hidden automatically</p>
|
|
67
67
|
</div>
|
|
68
68
|
<div class="bg-white p-6 rounded-lg shadow-sm">
|
|
69
69
|
<div class="text-4xl mb-4">🔄</div>
|
|
@@ -73,7 +73,7 @@
|
|
|
73
73
|
<div class="bg-white p-6 rounded-lg shadow-sm">
|
|
74
74
|
<div class="text-4xl mb-4">🧪</div>
|
|
75
75
|
<h3 class="text-xl font-semibold mb-2">Well Tested</h3>
|
|
76
|
-
<p class="text-gray-600">
|
|
76
|
+
<p class="text-gray-600">116 RSpec tests with 100% pass rate, built with TDD</p>
|
|
77
77
|
</div>
|
|
78
78
|
</div>
|
|
79
79
|
</div>
|
|
@@ -114,7 +114,11 @@
|
|
|
114
114
|
<li><code class="bg-gray-100 px-2 py-1 rounded">harbinger scan --path [PATH]</code> - Scan a project and show EOL status</li>
|
|
115
115
|
<li><code class="bg-gray-100 px-2 py-1 rounded">harbinger scan --save</code> - Save project for tracking</li>
|
|
116
116
|
<li><code class="bg-gray-100 px-2 py-1 rounded">harbinger scan --recursive</code> - Scan all projects in a directory</li>
|
|
117
|
-
<li><code class="bg-gray-100 px-2 py-1 rounded">harbinger show</code> - View dashboard
|
|
117
|
+
<li><code class="bg-gray-100 px-2 py-1 rounded">harbinger show [PROJECT] [-v]</code> - View dashboard (filter by name/path, -v shows paths)</li>
|
|
118
|
+
<li><code class="bg-gray-100 px-2 py-1 rounded">harbinger show --format json|csv</code> - Export data to JSON or CSV</li>
|
|
119
|
+
<li><code class="bg-gray-100 px-2 py-1 rounded">harbinger show --format json -o report.json</code> - Save export to file</li>
|
|
120
|
+
<li><code class="bg-gray-100 px-2 py-1 rounded">harbinger rescan</code> - Re-scan all tracked projects and update versions</li>
|
|
121
|
+
<li><code class="bg-gray-100 px-2 py-1 rounded">harbinger remove PROJECT</code> - Remove a project from tracking</li>
|
|
118
122
|
<li><code class="bg-gray-100 px-2 py-1 rounded">harbinger update</code> - Force refresh EOL data from API</li>
|
|
119
123
|
<li><code class="bg-gray-100 px-2 py-1 rounded">harbinger version</code> - Show harbinger version</li>
|
|
120
124
|
</ul>
|
|
@@ -127,21 +131,21 @@
|
|
|
127
131
|
<h2 class="text-3xl font-bold mb-8 text-center">Roadmap</h2>
|
|
128
132
|
<div class="grid md:grid-cols-3 gap-8">
|
|
129
133
|
<div>
|
|
130
|
-
<h3 class="text-xl font-semibold mb-4 text-green-600">✅ V0.
|
|
134
|
+
<h3 class="text-xl font-semibold mb-4 text-green-600">✅ V0.4.0 (Current)</h3>
|
|
131
135
|
<ul class="space-y-2 text-gray-600">
|
|
132
|
-
<li>•
|
|
133
|
-
<li>•
|
|
134
|
-
<li>•
|
|
135
|
-
<li>•
|
|
136
|
+
<li>• Export to JSON/CSV</li>
|
|
137
|
+
<li>• Docker Compose detection</li>
|
|
138
|
+
<li>• Redis detection</li>
|
|
139
|
+
<li>• MongoDB detection</li>
|
|
136
140
|
</ul>
|
|
137
141
|
</div>
|
|
138
142
|
<div>
|
|
139
|
-
<h3 class="text-xl font-semibold mb-4 text-blue-600">📋 V0.3.0
|
|
143
|
+
<h3 class="text-xl font-semibold mb-4 text-blue-600">📋 V0.3.0</h3>
|
|
140
144
|
<ul class="space-y-2 text-gray-600">
|
|
141
145
|
<li>• PostgreSQL detection</li>
|
|
142
146
|
<li>• MySQL detection</li>
|
|
143
147
|
<li>• Rescan command</li>
|
|
144
|
-
<li>•
|
|
148
|
+
<li>• Smart dashboard filtering</li>
|
|
145
149
|
</ul>
|
|
146
150
|
</div>
|
|
147
151
|
<div>
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require "English"
|
|
3
4
|
require "yaml"
|
|
4
5
|
|
|
5
6
|
module Harbinger
|
|
@@ -17,7 +18,11 @@ module Harbinger
|
|
|
17
18
|
def detect
|
|
18
19
|
return nil unless database_detected?
|
|
19
20
|
|
|
20
|
-
# Try
|
|
21
|
+
# Try docker-compose.yml first (most accurate for Docker-based projects)
|
|
22
|
+
version = detect_from_docker_compose
|
|
23
|
+
return version if version
|
|
24
|
+
|
|
25
|
+
# Try shell command (actual database version for non-Docker setups)
|
|
21
26
|
version = detect_from_shell
|
|
22
27
|
return version if version
|
|
23
28
|
|
|
@@ -68,6 +73,11 @@ module Harbinger
|
|
|
68
73
|
raise NotImplementedError, "Subclasses must implement detect_from_shell"
|
|
69
74
|
end
|
|
70
75
|
|
|
76
|
+
# Optional method - subclasses can override to detect from docker-compose.yml
|
|
77
|
+
def detect_from_docker_compose
|
|
78
|
+
nil
|
|
79
|
+
end
|
|
80
|
+
|
|
71
81
|
# Abstract method - must be implemented by subclasses
|
|
72
82
|
# Detects version from Gemfile.lock gem version
|
|
73
83
|
def detect_from_gemfile_lock
|
|
@@ -81,7 +91,7 @@ module Harbinger
|
|
|
81
91
|
|
|
82
92
|
content = File.read(database_yml_path)
|
|
83
93
|
YAML.safe_load(content, aliases: true)
|
|
84
|
-
rescue
|
|
94
|
+
rescue StandardError
|
|
85
95
|
nil
|
|
86
96
|
end
|
|
87
97
|
|
|
@@ -112,7 +122,7 @@ module Harbinger
|
|
|
112
122
|
# Execute shell command safely
|
|
113
123
|
def execute_command(command)
|
|
114
124
|
output = `#{command} 2>&1`.strip
|
|
115
|
-
return nil unless
|
|
125
|
+
return nil unless $CHILD_STATUS.success?
|
|
116
126
|
|
|
117
127
|
output
|
|
118
128
|
rescue StandardError
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "yaml"
|
|
4
|
+
|
|
5
|
+
module Harbinger
|
|
6
|
+
module Analyzers
|
|
7
|
+
# Detects versions from docker-compose.yml and Dockerfile
|
|
8
|
+
class DockerComposeDetector
|
|
9
|
+
attr_reader :project_path
|
|
10
|
+
|
|
11
|
+
def initialize(project_path)
|
|
12
|
+
@project_path = project_path
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
# Extract version from a Docker image in docker-compose.yml
|
|
16
|
+
# e.g., "postgres:16-alpine" => "16", "mysql:8.0" => "8.0"
|
|
17
|
+
def image_version(image_pattern)
|
|
18
|
+
compose = parse_docker_compose
|
|
19
|
+
return nil unless compose
|
|
20
|
+
|
|
21
|
+
services = compose["services"]
|
|
22
|
+
return nil unless services
|
|
23
|
+
|
|
24
|
+
services.each_value do |service|
|
|
25
|
+
image = service["image"]
|
|
26
|
+
next unless image
|
|
27
|
+
|
|
28
|
+
if image.match?(/^#{image_pattern}[:\d]/)
|
|
29
|
+
version = extract_version_from_image(image)
|
|
30
|
+
return version if version
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
nil
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# Extract Ruby version from Dockerfile
|
|
38
|
+
# e.g., "FROM ruby:3.4.7-slim" => "3.4.7"
|
|
39
|
+
def ruby_version_from_dockerfile
|
|
40
|
+
dockerfile = read_dockerfile
|
|
41
|
+
return nil unless dockerfile
|
|
42
|
+
|
|
43
|
+
# Match patterns like:
|
|
44
|
+
# FROM ruby:3.4.7
|
|
45
|
+
# FROM ruby:3.4.7-slim
|
|
46
|
+
# FROM ruby:3.4.7-alpine
|
|
47
|
+
match = dockerfile.match(/^FROM\s+ruby:(\d+\.\d+(?:\.\d+)?)/i)
|
|
48
|
+
match[1] if match
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# Extract Rails version from Dockerfile (rare, but possible)
|
|
52
|
+
def rails_version_from_dockerfile
|
|
53
|
+
dockerfile = read_dockerfile
|
|
54
|
+
return nil unless dockerfile
|
|
55
|
+
|
|
56
|
+
# Match patterns like:
|
|
57
|
+
# FROM rails:7.0
|
|
58
|
+
# ARG RAILS_VERSION=7.0.8
|
|
59
|
+
match = dockerfile.match(/^FROM\s+rails:(\d+\.\d+(?:\.\d+)?)/i)
|
|
60
|
+
return match[1] if match
|
|
61
|
+
|
|
62
|
+
match = dockerfile.match(/RAILS_VERSION[=:](\d+\.\d+(?:\.\d+)?)/i)
|
|
63
|
+
match[1] if match
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# Check if docker-compose.yml exists
|
|
67
|
+
def docker_compose_exists?
|
|
68
|
+
docker_compose_path != nil
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# Check if Dockerfile exists
|
|
72
|
+
def dockerfile_exists?
|
|
73
|
+
File.exist?(File.join(project_path, "Dockerfile"))
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
private
|
|
77
|
+
|
|
78
|
+
def docker_compose_path
|
|
79
|
+
paths = [
|
|
80
|
+
File.join(project_path, "docker-compose.yml"),
|
|
81
|
+
File.join(project_path, "docker-compose.yaml"),
|
|
82
|
+
File.join(project_path, "compose.yml"),
|
|
83
|
+
File.join(project_path, "compose.yaml")
|
|
84
|
+
]
|
|
85
|
+
paths.find { |p| File.exist?(p) }
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def parse_docker_compose
|
|
89
|
+
path = docker_compose_path
|
|
90
|
+
return nil unless path
|
|
91
|
+
|
|
92
|
+
YAML.safe_load(File.read(path), aliases: true)
|
|
93
|
+
rescue StandardError
|
|
94
|
+
nil
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def read_dockerfile
|
|
98
|
+
path = File.join(project_path, "Dockerfile")
|
|
99
|
+
return nil unless File.exist?(path)
|
|
100
|
+
|
|
101
|
+
File.read(path)
|
|
102
|
+
rescue StandardError
|
|
103
|
+
nil
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
# Extract version number from Docker image tag
|
|
107
|
+
# "postgres:16-alpine" => "16"
|
|
108
|
+
# "postgres:16.2" => "16.2"
|
|
109
|
+
# "mysql:8.0.33" => "8.0.33"
|
|
110
|
+
# "redis:7-alpine" => "7"
|
|
111
|
+
def extract_version_from_image(image)
|
|
112
|
+
return nil unless image.include?(":")
|
|
113
|
+
|
|
114
|
+
tag = image.split(":").last
|
|
115
|
+
# Extract leading version number (digits and dots)
|
|
116
|
+
match = tag.match(/^(\d+(?:\.\d+)*)/)
|
|
117
|
+
match[1] if match
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
end
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "docker_compose_detector"
|
|
4
|
+
|
|
5
|
+
module Harbinger
|
|
6
|
+
module Analyzers
|
|
7
|
+
# Detects MongoDB version from projects
|
|
8
|
+
class MongoDetector
|
|
9
|
+
attr_reader :project_path
|
|
10
|
+
|
|
11
|
+
def initialize(project_path)
|
|
12
|
+
@project_path = project_path
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
# Main detection method - returns version string or nil
|
|
16
|
+
def detect
|
|
17
|
+
return nil unless mongo_detected?
|
|
18
|
+
|
|
19
|
+
# Try docker-compose.yml first
|
|
20
|
+
version = detect_from_docker_compose
|
|
21
|
+
return version if version
|
|
22
|
+
|
|
23
|
+
# Try shell command
|
|
24
|
+
version = detect_from_shell
|
|
25
|
+
return version if version
|
|
26
|
+
|
|
27
|
+
# Fallback to gem version
|
|
28
|
+
detect_from_gemfile_lock
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# Check if MongoDB is used in this project
|
|
32
|
+
def mongo_detected?
|
|
33
|
+
gemfile_has_mongo? || docker_compose_has_mongo?
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
private
|
|
37
|
+
|
|
38
|
+
def detect_from_docker_compose
|
|
39
|
+
docker = DockerComposeDetector.new(project_path)
|
|
40
|
+
docker.image_version("mongo")
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def detect_from_shell
|
|
44
|
+
# Try mongosh first (modern shell, MongoDB 5+)
|
|
45
|
+
output = `mongosh --version 2>&1`.strip
|
|
46
|
+
return output if $CHILD_STATUS.success? && output.match?(/^\d+\.\d+/)
|
|
47
|
+
|
|
48
|
+
# Try legacy mongo shell
|
|
49
|
+
output = `mongo --version 2>&1`.strip
|
|
50
|
+
if $CHILD_STATUS.success?
|
|
51
|
+
match = output.match(/MongoDB shell version v?(\d+\.\d+(?:\.\d+)?)/)
|
|
52
|
+
return match[1] if match
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# Fall back to mongod (server)
|
|
56
|
+
output = `mongod --version 2>&1`.strip
|
|
57
|
+
return nil unless $CHILD_STATUS.success?
|
|
58
|
+
|
|
59
|
+
match = output.match(/db version v(\d+\.\d+(?:\.\d+)?)/)
|
|
60
|
+
match[1] if match
|
|
61
|
+
rescue StandardError
|
|
62
|
+
nil
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def detect_from_gemfile_lock
|
|
66
|
+
content = read_gemfile_lock
|
|
67
|
+
return nil unless content
|
|
68
|
+
|
|
69
|
+
# Look for mongoid gem version first (most common for Rails)
|
|
70
|
+
match = content.match(/^\s{4}mongoid\s+\(([^)]+)\)/)
|
|
71
|
+
return "#{match[1]} (mongoid gem)" if match
|
|
72
|
+
|
|
73
|
+
# Fall back to mongo gem
|
|
74
|
+
match = content.match(/^\s{4}mongo\s+\(([^)]+)\)/)
|
|
75
|
+
return "#{match[1]} (mongo gem)" if match
|
|
76
|
+
|
|
77
|
+
nil
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def gemfile_has_mongo?
|
|
81
|
+
content = read_gemfile_lock
|
|
82
|
+
return false unless content
|
|
83
|
+
|
|
84
|
+
content.include?("mongoid (") || content.include?("mongo (")
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def docker_compose_has_mongo?
|
|
88
|
+
docker = DockerComposeDetector.new(project_path)
|
|
89
|
+
return false unless docker.docker_compose_exists?
|
|
90
|
+
|
|
91
|
+
docker.image_version("mongo") != nil
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def read_gemfile_lock
|
|
95
|
+
path = File.join(project_path, "Gemfile.lock")
|
|
96
|
+
return nil unless File.exist?(path)
|
|
97
|
+
|
|
98
|
+
File.read(path)
|
|
99
|
+
rescue StandardError
|
|
100
|
+
nil
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
end
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require_relative "database_detector"
|
|
4
|
+
require_relative "docker_compose_detector"
|
|
4
5
|
|
|
5
6
|
module Harbinger
|
|
6
7
|
module Analyzers
|
|
@@ -10,7 +11,13 @@ module Harbinger
|
|
|
10
11
|
protected
|
|
11
12
|
|
|
12
13
|
def adapter_name
|
|
13
|
-
[
|
|
14
|
+
%w[mysql2 trilogy]
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def detect_from_docker_compose
|
|
18
|
+
docker = DockerComposeDetector.new(project_path)
|
|
19
|
+
# Try mysql first, then mariadb
|
|
20
|
+
docker.image_version("mysql") || docker.image_version("mariadb")
|
|
14
21
|
end
|
|
15
22
|
|
|
16
23
|
def detect_from_shell
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require_relative "database_detector"
|
|
4
|
+
require_relative "docker_compose_detector"
|
|
4
5
|
|
|
5
6
|
module Harbinger
|
|
6
7
|
module Analyzers
|
|
@@ -12,6 +13,11 @@ module Harbinger
|
|
|
12
13
|
"postgresql"
|
|
13
14
|
end
|
|
14
15
|
|
|
16
|
+
def detect_from_docker_compose
|
|
17
|
+
docker = DockerComposeDetector.new(project_path)
|
|
18
|
+
docker.image_version("postgres")
|
|
19
|
+
end
|
|
20
|
+
|
|
15
21
|
def detect_from_shell
|
|
16
22
|
# Skip shell command if database is remote
|
|
17
23
|
# (shell gives client version, not server version)
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "docker_compose_detector"
|
|
4
|
+
|
|
5
|
+
module Harbinger
|
|
6
|
+
module Analyzers
|
|
7
|
+
# Detects Redis version from projects
|
|
8
|
+
class RedisDetector
|
|
9
|
+
attr_reader :project_path
|
|
10
|
+
|
|
11
|
+
def initialize(project_path)
|
|
12
|
+
@project_path = project_path
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
# Main detection method - returns version string or nil
|
|
16
|
+
def detect
|
|
17
|
+
return nil unless redis_detected?
|
|
18
|
+
|
|
19
|
+
# Try docker-compose.yml first
|
|
20
|
+
version = detect_from_docker_compose
|
|
21
|
+
return version if version
|
|
22
|
+
|
|
23
|
+
# Try shell command
|
|
24
|
+
version = detect_from_shell
|
|
25
|
+
return version if version
|
|
26
|
+
|
|
27
|
+
# Fallback to gem version
|
|
28
|
+
detect_from_gemfile_lock
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# Check if Redis is used in this project
|
|
32
|
+
def redis_detected?
|
|
33
|
+
gemfile_has_redis? || docker_compose_has_redis?
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
private
|
|
37
|
+
|
|
38
|
+
def detect_from_docker_compose
|
|
39
|
+
docker = DockerComposeDetector.new(project_path)
|
|
40
|
+
docker.image_version("redis")
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def detect_from_shell
|
|
44
|
+
# Try redis-cli first (more commonly available)
|
|
45
|
+
output = `redis-cli -v 2>&1`.strip
|
|
46
|
+
if $CHILD_STATUS.success?
|
|
47
|
+
# Parse: "redis-cli 7.0.5"
|
|
48
|
+
match = output.match(/redis-cli\s+(\d+\.\d+(?:\.\d+)?)/)
|
|
49
|
+
return match[1] if match
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# Fall back to redis-server
|
|
53
|
+
output = `redis-server --version 2>&1`.strip
|
|
54
|
+
return nil unless $CHILD_STATUS.success?
|
|
55
|
+
|
|
56
|
+
# Parse: "Redis server v=7.2.4 sha=..."
|
|
57
|
+
match = output.match(/v=(\d+\.\d+(?:\.\d+)?)/)
|
|
58
|
+
match[1] if match
|
|
59
|
+
rescue StandardError
|
|
60
|
+
nil
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def detect_from_gemfile_lock
|
|
64
|
+
content = read_gemfile_lock
|
|
65
|
+
return nil unless content
|
|
66
|
+
|
|
67
|
+
# Look for redis gem version
|
|
68
|
+
match = content.match(/^\s{4}redis\s+\(([^)]+)\)/)
|
|
69
|
+
return "#{match[1]} (gem)" if match
|
|
70
|
+
|
|
71
|
+
nil
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def gemfile_has_redis?
|
|
75
|
+
content = read_gemfile_lock
|
|
76
|
+
return false unless content
|
|
77
|
+
|
|
78
|
+
content.include?("redis (")
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def docker_compose_has_redis?
|
|
82
|
+
docker = DockerComposeDetector.new(project_path)
|
|
83
|
+
return false unless docker.docker_compose_exists?
|
|
84
|
+
|
|
85
|
+
docker.image_version("redis") != nil
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def read_gemfile_lock
|
|
89
|
+
path = File.join(project_path, "Gemfile.lock")
|
|
90
|
+
return nil unless File.exist?(path)
|
|
91
|
+
|
|
92
|
+
File.read(path)
|
|
93
|
+
rescue StandardError
|
|
94
|
+
nil
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
end
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require_relative "docker_compose_detector"
|
|
4
|
+
|
|
3
5
|
module Harbinger
|
|
4
6
|
module Analyzers
|
|
5
7
|
class RubyDetector
|
|
@@ -10,7 +12,8 @@ module Harbinger
|
|
|
10
12
|
def detect
|
|
11
13
|
detect_from_ruby_version ||
|
|
12
14
|
detect_from_gemfile ||
|
|
13
|
-
detect_from_gemfile_lock
|
|
15
|
+
detect_from_gemfile_lock ||
|
|
16
|
+
detect_from_dockerfile
|
|
14
17
|
end
|
|
15
18
|
|
|
16
19
|
def ruby_detected?
|
|
@@ -61,6 +64,11 @@ module Harbinger
|
|
|
61
64
|
# Remove patch level suffix (e.g., "p223")
|
|
62
65
|
version.sub(/p\d+$/, "")
|
|
63
66
|
end
|
|
67
|
+
|
|
68
|
+
def detect_from_dockerfile
|
|
69
|
+
docker = DockerComposeDetector.new(project_path)
|
|
70
|
+
docker.ruby_version_from_dockerfile
|
|
71
|
+
end
|
|
64
72
|
end
|
|
65
73
|
end
|
|
66
74
|
end
|