java-dependency-analyzer 1.0.0__tar.gz
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.
- java_dependency_analyzer-1.0.0/PKG-INFO +193 -0
- java_dependency_analyzer-1.0.0/README.md +175 -0
- java_dependency_analyzer-1.0.0/java_dependency_analyzer/__init__.py +11 -0
- java_dependency_analyzer-1.0.0/java_dependency_analyzer/cache/__init__.py +11 -0
- java_dependency_analyzer-1.0.0/java_dependency_analyzer/cache/db.py +101 -0
- java_dependency_analyzer-1.0.0/java_dependency_analyzer/cache/vulnerability_cache.py +156 -0
- java_dependency_analyzer-1.0.0/java_dependency_analyzer/cli.py +394 -0
- java_dependency_analyzer-1.0.0/java_dependency_analyzer/models/__init__.py +11 -0
- java_dependency_analyzer-1.0.0/java_dependency_analyzer/models/dependency.py +80 -0
- java_dependency_analyzer-1.0.0/java_dependency_analyzer/models/report.py +108 -0
- java_dependency_analyzer-1.0.0/java_dependency_analyzer/parsers/__init__.py +11 -0
- java_dependency_analyzer-1.0.0/java_dependency_analyzer/parsers/base.py +150 -0
- java_dependency_analyzer-1.0.0/java_dependency_analyzer/parsers/gradle_dep_tree_parser.py +125 -0
- java_dependency_analyzer-1.0.0/java_dependency_analyzer/parsers/gradle_parser.py +206 -0
- java_dependency_analyzer-1.0.0/java_dependency_analyzer/parsers/maven_dep_tree_parser.py +123 -0
- java_dependency_analyzer-1.0.0/java_dependency_analyzer/parsers/maven_parser.py +182 -0
- java_dependency_analyzer-1.0.0/java_dependency_analyzer/reporters/__init__.py +11 -0
- java_dependency_analyzer-1.0.0/java_dependency_analyzer/reporters/base.py +33 -0
- java_dependency_analyzer-1.0.0/java_dependency_analyzer/reporters/html_reporter.py +82 -0
- java_dependency_analyzer-1.0.0/java_dependency_analyzer/reporters/json_reporter.py +52 -0
- java_dependency_analyzer-1.0.0/java_dependency_analyzer/reporters/templates/report.html +406 -0
- java_dependency_analyzer-1.0.0/java_dependency_analyzer/resolvers/__init__.py +11 -0
- java_dependency_analyzer-1.0.0/java_dependency_analyzer/resolvers/transitive.py +276 -0
- java_dependency_analyzer-1.0.0/java_dependency_analyzer/scanners/__init__.py +11 -0
- java_dependency_analyzer-1.0.0/java_dependency_analyzer/scanners/base.py +102 -0
- java_dependency_analyzer-1.0.0/java_dependency_analyzer/scanners/ghsa_scanner.py +204 -0
- java_dependency_analyzer-1.0.0/java_dependency_analyzer/scanners/osv_scanner.py +167 -0
- java_dependency_analyzer-1.0.0/java_dependency_analyzer/util/__init__.py +11 -0
- java_dependency_analyzer-1.0.0/java_dependency_analyzer/util/logger.py +48 -0
- java_dependency_analyzer-1.0.0/pyproject.toml +37 -0
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: java-dependency-analyzer
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Java Dependency Analyzer is a tool that inspects dependencies.
|
|
5
|
+
Author: Ron Webb
|
|
6
|
+
Author-email: ron@ronella.xyz
|
|
7
|
+
Requires-Python: >=3.14
|
|
8
|
+
Classifier: Programming Language :: Python :: 3
|
|
9
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
10
|
+
Requires-Dist: beautifulsoup4 (>=4.14.3,<5.0.0)
|
|
11
|
+
Requires-Dist: click (>=8.3.1,<9.0.0)
|
|
12
|
+
Requires-Dist: httpx (>=0.28.1,<0.29.0)
|
|
13
|
+
Requires-Dist: jinja2 (>=3.1.6,<4.0.0)
|
|
14
|
+
Requires-Dist: lxml (>=6.0.2,<7.0.0)
|
|
15
|
+
Requires-Dist: python-dotenv (>=1.2.2,<2.0.0)
|
|
16
|
+
Description-Content-Type: text/markdown
|
|
17
|
+
|
|
18
|
+
# Java Dependency Analyzer 1.0.0
|
|
19
|
+
|
|
20
|
+
> A Python CLI tool that inspects Java dependency hierarchies in Maven and Gradle projects and reports known vulnerabilities.
|
|
21
|
+
|
|
22
|
+
## Prerequisites
|
|
23
|
+
|
|
24
|
+
- Python `^3.14`
|
|
25
|
+
- [Poetry](https://python-poetry.org/) `2.2`
|
|
26
|
+
|
|
27
|
+
## Installation
|
|
28
|
+
|
|
29
|
+
Clone the repository and install all dependencies:
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
git clone <repository-url>
|
|
33
|
+
cd java-dependency-analyzer
|
|
34
|
+
poetry install
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## Usage
|
|
38
|
+
|
|
39
|
+
```
|
|
40
|
+
jda <COMMAND> [OPTIONS] [FILE]
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
`COMMAND` is one of `gradle` or `maven`.
|
|
44
|
+
|
|
45
|
+
### gradle
|
|
46
|
+
|
|
47
|
+
```
|
|
48
|
+
jda gradle [OPTIONS] [FILE]
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
`FILE` is the path to a `build.gradle` or `build.gradle.kts` file.
|
|
52
|
+
Omit `FILE` when supplying `--dependencies`.
|
|
53
|
+
|
|
54
|
+
### maven
|
|
55
|
+
|
|
56
|
+
```
|
|
57
|
+
jda maven [OPTIONS] [FILE]
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
`FILE` is the path to a `pom.xml` file.
|
|
61
|
+
Omit `FILE` when supplying `--dependencies`.
|
|
62
|
+
|
|
63
|
+
### Options (both subcommands)
|
|
64
|
+
|
|
65
|
+
| Option | Short | Default | Description |
|
|
66
|
+
|---|---|---|---|
|
|
67
|
+
| `--dependencies` | `-d` | | Path to a pre-resolved dependency tree text file (see below). When supplied, parsing and transitive resolution are skipped. |
|
|
68
|
+
| `--output-format` | `-f` | `all` | Report format: `json`, `html`, or `all` (both). |
|
|
69
|
+
| `--output-dir` | `-o` | `.` | Directory to write the report file(s) into. |
|
|
70
|
+
| `--no-transitive` | | `false` | Skip transitive dependency resolution; analyse direct dependencies only. |
|
|
71
|
+
| `--verbose` | `-v` | `false` | Print progress messages to the console. |
|
|
72
|
+
| `--rebuild-cache` | | `false` | Delete the vulnerability cache before scanning. |
|
|
73
|
+
| `--cache-ttl` | | `7` | Cache TTL in days. Set to `0` to disable caching. |
|
|
74
|
+
|
|
75
|
+
### Pre-resolved dependency trees (`--dependencies`)
|
|
76
|
+
|
|
77
|
+
When a Gradle or Maven project already has a dependency tree available (e.g. from CI), you can pass it directly to skip the parser and transitive resolver:
|
|
78
|
+
|
|
79
|
+
- **Gradle**: generate with `gradle dependencies --configuration runtimeClasspath > gradle.txt`
|
|
80
|
+
- **Maven**: generate with `mvn dependency:tree -Dscope=runtime > maven.txt`
|
|
81
|
+
|
|
82
|
+
The report will reflect the exact tree from the file, including all transitive dependencies.
|
|
83
|
+
|
|
84
|
+
### Examples
|
|
85
|
+
|
|
86
|
+
Analyse a Maven POM and produce both JSON and HTML reports in the current directory:
|
|
87
|
+
|
|
88
|
+
```bash
|
|
89
|
+
jda maven pom.xml
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
Analyse a Gradle build file and write only an HTML report to `./reports/`:
|
|
93
|
+
|
|
94
|
+
```bash
|
|
95
|
+
jda gradle build.gradle -f html -o reports/
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
Analyse direct dependencies only, with verbose output:
|
|
99
|
+
|
|
100
|
+
```bash
|
|
101
|
+
jda gradle build.gradle.kts --no-transitive -v
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
Scan using a pre-resolved Gradle dependency tree (skips transitive resolution):
|
|
105
|
+
|
|
106
|
+
```bash
|
|
107
|
+
jda gradle --dependencies runtime.txt -f json -o reports/
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
Scan using a pre-resolved Maven dependency tree (skips transitive resolution):
|
|
111
|
+
|
|
112
|
+
```bash
|
|
113
|
+
jda maven --dependencies maven.txt -f json -o reports/
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
## Architecture
|
|
117
|
+
|
|
118
|
+
```mermaid
|
|
119
|
+
graph TD
|
|
120
|
+
CLI["jda CLI (cli.py)"] --> Parser["DependencyParser (ABC)"]
|
|
121
|
+
Parser --> MavenParser
|
|
122
|
+
Parser --> GradleParser
|
|
123
|
+
Parser --> MavenDepTreeParser
|
|
124
|
+
Parser --> GradleDepTreeParser
|
|
125
|
+
CLI --> Resolver["TransitiveResolver<br/>(Maven Central)"]
|
|
126
|
+
CLI --> Scanner["VulnerabilityScanner (ABC)"]
|
|
127
|
+
Scanner --> OsvScanner["OsvScanner<br/>(OSV.dev API)"]
|
|
128
|
+
Scanner --> GhsaScanner["GhsaScanner<br/>(GitHub Advisory DB)"]
|
|
129
|
+
OsvScanner --> Cache["VulnerabilityCache<br/>(SQLite)"]
|
|
130
|
+
GhsaScanner --> Cache
|
|
131
|
+
CLI --> Reporter["Reporter (ABC)"]
|
|
132
|
+
Reporter --> JsonReporter
|
|
133
|
+
Reporter --> HtmlReporter
|
|
134
|
+
MavenParser --> Dependency["Dependency / Vulnerability<br/>Dataclasses"]
|
|
135
|
+
GradleParser --> Dependency
|
|
136
|
+
MavenDepTreeParser --> Dependency
|
|
137
|
+
GradleDepTreeParser --> Dependency
|
|
138
|
+
Resolver --> Dependency
|
|
139
|
+
OsvScanner --> Dependency
|
|
140
|
+
GhsaScanner --> Dependency
|
|
141
|
+
JsonReporter --> ScanResult["ScanResult"]
|
|
142
|
+
HtmlReporter --> ScanResult
|
|
143
|
+
Dependency --> ScanResult
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
### Components
|
|
147
|
+
|
|
148
|
+
| Component | Location | Responsibility |
|
|
149
|
+
|---|---|---|
|
|
150
|
+
| CLI | `java_dependency_analyzer/cli.py` | Entry point (`gradle` / `maven` subcommands); orchestrates parsing, resolving, scanning, and reporting. |
|
|
151
|
+
| `MavenParser` | `parsers/maven_parser.py` | Parses `pom.xml`, resolves `${property}` placeholders, filters by runtime scope. |
|
|
152
|
+
| `GradleParser` | `parsers/gradle_parser.py` | Parses Groovy DSL (`build.gradle`) and Kotlin DSL (`build.gradle.kts`) files. |
|
|
153
|
+
| `MavenDepTreeParser` | `parsers/maven_dep_tree_parser.py` | Parses `mvn dependency:tree` text output into a full dependency tree. |
|
|
154
|
+
| `GradleDepTreeParser` | `parsers/gradle_dep_tree_parser.py` | Parses `gradle dependencies` text output into a full dependency tree. |
|
|
155
|
+
| `TransitiveResolver` | `resolvers/transitive.py` | Fetches transitive dependencies by downloading POM files from Maven Central. |
|
|
156
|
+
| `OsvScanner` | `scanners/osv_scanner.py` | Queries the [OSV.dev](https://osv.dev/) batch API for known CVEs. |
|
|
157
|
+
| `GhsaScanner` | `scanners/ghsa_scanner.py` | Queries the [GitHub Advisory Database](https://github.com/advisories) REST API for security advisories. |
|
|
158
|
+
| `VulnerabilityCache` | `cache/vulnerability_cache.py` | SQLite-backed cache for raw vulnerability API payloads with configurable TTL. |
|
|
159
|
+
| `DatabaseManager` | `cache/db.py` | Manages SQLite connection lifecycle and schema initialisation. |
|
|
160
|
+
| `JsonReporter` | `reporters/json_reporter.py` | Writes a `ScanResult` to a JSON file. |
|
|
161
|
+
| `HtmlReporter` | `reporters/html_reporter.py` | Renders a `ScanResult` to a styled HTML report via a Jinja2 template. |
|
|
162
|
+
|
|
163
|
+
## Development Setup
|
|
164
|
+
|
|
165
|
+
Install all dependencies (including dev tools):
|
|
166
|
+
|
|
167
|
+
```bash
|
|
168
|
+
poetry install
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
### Running Tests
|
|
172
|
+
|
|
173
|
+
Run the full test suite with coverage and generate an HTML report:
|
|
174
|
+
|
|
175
|
+
```bash
|
|
176
|
+
poetry run pytest --cov=java_dependency_analyzer tests --cov-report html
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
### Code Quality
|
|
180
|
+
|
|
181
|
+
Format and lint the source code (linter must score 10/10):
|
|
182
|
+
|
|
183
|
+
```bash
|
|
184
|
+
poetry run black java_dependency_analyzer
|
|
185
|
+
poetry run pylint java_dependency_analyzer
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
## [Changelog](CHANGELOG.md)
|
|
189
|
+
|
|
190
|
+
## Author
|
|
191
|
+
|
|
192
|
+
Ron Webb <ron@ronella.xyz>
|
|
193
|
+
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
# Java Dependency Analyzer 1.0.0
|
|
2
|
+
|
|
3
|
+
> A Python CLI tool that inspects Java dependency hierarchies in Maven and Gradle projects and reports known vulnerabilities.
|
|
4
|
+
|
|
5
|
+
## Prerequisites
|
|
6
|
+
|
|
7
|
+
- Python `^3.14`
|
|
8
|
+
- [Poetry](https://python-poetry.org/) `2.2`
|
|
9
|
+
|
|
10
|
+
## Installation
|
|
11
|
+
|
|
12
|
+
Clone the repository and install all dependencies:
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
git clone <repository-url>
|
|
16
|
+
cd java-dependency-analyzer
|
|
17
|
+
poetry install
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Usage
|
|
21
|
+
|
|
22
|
+
```
|
|
23
|
+
jda <COMMAND> [OPTIONS] [FILE]
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
`COMMAND` is one of `gradle` or `maven`.
|
|
27
|
+
|
|
28
|
+
### gradle
|
|
29
|
+
|
|
30
|
+
```
|
|
31
|
+
jda gradle [OPTIONS] [FILE]
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
`FILE` is the path to a `build.gradle` or `build.gradle.kts` file.
|
|
35
|
+
Omit `FILE` when supplying `--dependencies`.
|
|
36
|
+
|
|
37
|
+
### maven
|
|
38
|
+
|
|
39
|
+
```
|
|
40
|
+
jda maven [OPTIONS] [FILE]
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
`FILE` is the path to a `pom.xml` file.
|
|
44
|
+
Omit `FILE` when supplying `--dependencies`.
|
|
45
|
+
|
|
46
|
+
### Options (both subcommands)
|
|
47
|
+
|
|
48
|
+
| Option | Short | Default | Description |
|
|
49
|
+
|---|---|---|---|
|
|
50
|
+
| `--dependencies` | `-d` | | Path to a pre-resolved dependency tree text file (see below). When supplied, parsing and transitive resolution are skipped. |
|
|
51
|
+
| `--output-format` | `-f` | `all` | Report format: `json`, `html`, or `all` (both). |
|
|
52
|
+
| `--output-dir` | `-o` | `.` | Directory to write the report file(s) into. |
|
|
53
|
+
| `--no-transitive` | | `false` | Skip transitive dependency resolution; analyse direct dependencies only. |
|
|
54
|
+
| `--verbose` | `-v` | `false` | Print progress messages to the console. |
|
|
55
|
+
| `--rebuild-cache` | | `false` | Delete the vulnerability cache before scanning. |
|
|
56
|
+
| `--cache-ttl` | | `7` | Cache TTL in days. Set to `0` to disable caching. |
|
|
57
|
+
|
|
58
|
+
### Pre-resolved dependency trees (`--dependencies`)
|
|
59
|
+
|
|
60
|
+
When a Gradle or Maven project already has a dependency tree available (e.g. from CI), you can pass it directly to skip the parser and transitive resolver:
|
|
61
|
+
|
|
62
|
+
- **Gradle**: generate with `gradle dependencies --configuration runtimeClasspath > gradle.txt`
|
|
63
|
+
- **Maven**: generate with `mvn dependency:tree -Dscope=runtime > maven.txt`
|
|
64
|
+
|
|
65
|
+
The report will reflect the exact tree from the file, including all transitive dependencies.
|
|
66
|
+
|
|
67
|
+
### Examples
|
|
68
|
+
|
|
69
|
+
Analyse a Maven POM and produce both JSON and HTML reports in the current directory:
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
jda maven pom.xml
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
Analyse a Gradle build file and write only an HTML report to `./reports/`:
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
jda gradle build.gradle -f html -o reports/
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
Analyse direct dependencies only, with verbose output:
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
jda gradle build.gradle.kts --no-transitive -v
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
Scan using a pre-resolved Gradle dependency tree (skips transitive resolution):
|
|
88
|
+
|
|
89
|
+
```bash
|
|
90
|
+
jda gradle --dependencies runtime.txt -f json -o reports/
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
Scan using a pre-resolved Maven dependency tree (skips transitive resolution):
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
jda maven --dependencies maven.txt -f json -o reports/
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
## Architecture
|
|
100
|
+
|
|
101
|
+
```mermaid
|
|
102
|
+
graph TD
|
|
103
|
+
CLI["jda CLI (cli.py)"] --> Parser["DependencyParser (ABC)"]
|
|
104
|
+
Parser --> MavenParser
|
|
105
|
+
Parser --> GradleParser
|
|
106
|
+
Parser --> MavenDepTreeParser
|
|
107
|
+
Parser --> GradleDepTreeParser
|
|
108
|
+
CLI --> Resolver["TransitiveResolver<br/>(Maven Central)"]
|
|
109
|
+
CLI --> Scanner["VulnerabilityScanner (ABC)"]
|
|
110
|
+
Scanner --> OsvScanner["OsvScanner<br/>(OSV.dev API)"]
|
|
111
|
+
Scanner --> GhsaScanner["GhsaScanner<br/>(GitHub Advisory DB)"]
|
|
112
|
+
OsvScanner --> Cache["VulnerabilityCache<br/>(SQLite)"]
|
|
113
|
+
GhsaScanner --> Cache
|
|
114
|
+
CLI --> Reporter["Reporter (ABC)"]
|
|
115
|
+
Reporter --> JsonReporter
|
|
116
|
+
Reporter --> HtmlReporter
|
|
117
|
+
MavenParser --> Dependency["Dependency / Vulnerability<br/>Dataclasses"]
|
|
118
|
+
GradleParser --> Dependency
|
|
119
|
+
MavenDepTreeParser --> Dependency
|
|
120
|
+
GradleDepTreeParser --> Dependency
|
|
121
|
+
Resolver --> Dependency
|
|
122
|
+
OsvScanner --> Dependency
|
|
123
|
+
GhsaScanner --> Dependency
|
|
124
|
+
JsonReporter --> ScanResult["ScanResult"]
|
|
125
|
+
HtmlReporter --> ScanResult
|
|
126
|
+
Dependency --> ScanResult
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
### Components
|
|
130
|
+
|
|
131
|
+
| Component | Location | Responsibility |
|
|
132
|
+
|---|---|---|
|
|
133
|
+
| CLI | `java_dependency_analyzer/cli.py` | Entry point (`gradle` / `maven` subcommands); orchestrates parsing, resolving, scanning, and reporting. |
|
|
134
|
+
| `MavenParser` | `parsers/maven_parser.py` | Parses `pom.xml`, resolves `${property}` placeholders, filters by runtime scope. |
|
|
135
|
+
| `GradleParser` | `parsers/gradle_parser.py` | Parses Groovy DSL (`build.gradle`) and Kotlin DSL (`build.gradle.kts`) files. |
|
|
136
|
+
| `MavenDepTreeParser` | `parsers/maven_dep_tree_parser.py` | Parses `mvn dependency:tree` text output into a full dependency tree. |
|
|
137
|
+
| `GradleDepTreeParser` | `parsers/gradle_dep_tree_parser.py` | Parses `gradle dependencies` text output into a full dependency tree. |
|
|
138
|
+
| `TransitiveResolver` | `resolvers/transitive.py` | Fetches transitive dependencies by downloading POM files from Maven Central. |
|
|
139
|
+
| `OsvScanner` | `scanners/osv_scanner.py` | Queries the [OSV.dev](https://osv.dev/) batch API for known CVEs. |
|
|
140
|
+
| `GhsaScanner` | `scanners/ghsa_scanner.py` | Queries the [GitHub Advisory Database](https://github.com/advisories) REST API for security advisories. |
|
|
141
|
+
| `VulnerabilityCache` | `cache/vulnerability_cache.py` | SQLite-backed cache for raw vulnerability API payloads with configurable TTL. |
|
|
142
|
+
| `DatabaseManager` | `cache/db.py` | Manages SQLite connection lifecycle and schema initialisation. |
|
|
143
|
+
| `JsonReporter` | `reporters/json_reporter.py` | Writes a `ScanResult` to a JSON file. |
|
|
144
|
+
| `HtmlReporter` | `reporters/html_reporter.py` | Renders a `ScanResult` to a styled HTML report via a Jinja2 template. |
|
|
145
|
+
|
|
146
|
+
## Development Setup
|
|
147
|
+
|
|
148
|
+
Install all dependencies (including dev tools):
|
|
149
|
+
|
|
150
|
+
```bash
|
|
151
|
+
poetry install
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
### Running Tests
|
|
155
|
+
|
|
156
|
+
Run the full test suite with coverage and generate an HTML report:
|
|
157
|
+
|
|
158
|
+
```bash
|
|
159
|
+
poetry run pytest --cov=java_dependency_analyzer tests --cov-report html
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
### Code Quality
|
|
163
|
+
|
|
164
|
+
Format and lint the source code (linter must score 10/10):
|
|
165
|
+
|
|
166
|
+
```bash
|
|
167
|
+
poetry run black java_dependency_analyzer
|
|
168
|
+
poetry run pylint java_dependency_analyzer
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
## [Changelog](CHANGELOG.md)
|
|
172
|
+
|
|
173
|
+
## Author
|
|
174
|
+
|
|
175
|
+
Ron Webb <ron@ronella.xyz>
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
"""
|
|
2
|
+
db module.
|
|
3
|
+
|
|
4
|
+
Manages the SQLite database lifecycle: path resolution, connection,
|
|
5
|
+
schema creation, and deletion.
|
|
6
|
+
|
|
7
|
+
:author: Ron Webb
|
|
8
|
+
:since: 1.0.0
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
import sqlite3
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
|
|
14
|
+
from ..util.logger import setup_logger
|
|
15
|
+
|
|
16
|
+
__author__ = "Ron Webb"
|
|
17
|
+
__since__ = "1.0.0"
|
|
18
|
+
|
|
19
|
+
_logger = setup_logger(__name__)
|
|
20
|
+
|
|
21
|
+
_DB_DIR = Path.home() / ".jda"
|
|
22
|
+
_DB_FILE = "cache.db"
|
|
23
|
+
|
|
24
|
+
_CREATE_TABLE_SQL = """
|
|
25
|
+
CREATE TABLE IF NOT EXISTS vulnerability_cache (
|
|
26
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
27
|
+
source TEXT NOT NULL,
|
|
28
|
+
group_id TEXT NOT NULL,
|
|
29
|
+
artifact_id TEXT NOT NULL,
|
|
30
|
+
version TEXT NOT NULL,
|
|
31
|
+
payload TEXT NOT NULL,
|
|
32
|
+
cached_at TEXT NOT NULL,
|
|
33
|
+
UNIQUE(source, group_id, artifact_id, version)
|
|
34
|
+
)
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
_CREATE_INDEX_SQL = """
|
|
38
|
+
CREATE INDEX IF NOT EXISTS idx_cache_lookup
|
|
39
|
+
ON vulnerability_cache(group_id, artifact_id, version, source)
|
|
40
|
+
"""
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def get_db_path() -> Path:
|
|
44
|
+
"""
|
|
45
|
+
Return the absolute path to the SQLite cache database file.
|
|
46
|
+
|
|
47
|
+
The default location is ``~/.jda/cache.db``.
|
|
48
|
+
|
|
49
|
+
:author: Ron Webb
|
|
50
|
+
:since: 1.0.0
|
|
51
|
+
"""
|
|
52
|
+
return _DB_DIR / _DB_FILE
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def get_connection() -> sqlite3.Connection:
|
|
56
|
+
"""
|
|
57
|
+
Open (and initialise) the SQLite database, creating the parent directory
|
|
58
|
+
and schema if they do not yet exist.
|
|
59
|
+
|
|
60
|
+
Returns a ``sqlite3.Connection`` with ``check_same_thread=False`` so the
|
|
61
|
+
connection can be shared within a single CLI invocation.
|
|
62
|
+
|
|
63
|
+
:author: Ron Webb
|
|
64
|
+
:since: 1.0.0
|
|
65
|
+
"""
|
|
66
|
+
db_path = get_db_path()
|
|
67
|
+
db_path.parent.mkdir(parents=True, exist_ok=True)
|
|
68
|
+
_logger.debug("Opening cache database at %s", db_path)
|
|
69
|
+
conn = sqlite3.connect(str(db_path), check_same_thread=False)
|
|
70
|
+
_initialise_schema(conn)
|
|
71
|
+
return conn
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def _initialise_schema(conn: sqlite3.Connection) -> None:
|
|
75
|
+
"""
|
|
76
|
+
Create the cache table and lookup index when they do not already exist.
|
|
77
|
+
|
|
78
|
+
:author: Ron Webb
|
|
79
|
+
:since: 1.0.0
|
|
80
|
+
"""
|
|
81
|
+
with conn:
|
|
82
|
+
conn.execute(_CREATE_TABLE_SQL)
|
|
83
|
+
conn.execute(_CREATE_INDEX_SQL)
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def delete_database() -> bool:
|
|
87
|
+
"""
|
|
88
|
+
Delete the SQLite cache database file.
|
|
89
|
+
|
|
90
|
+
Returns ``True`` if the file was deleted, ``False`` if it did not exist.
|
|
91
|
+
|
|
92
|
+
:author: Ron Webb
|
|
93
|
+
:since: 1.0.0
|
|
94
|
+
"""
|
|
95
|
+
db_path = get_db_path()
|
|
96
|
+
if db_path.exists():
|
|
97
|
+
db_path.unlink()
|
|
98
|
+
_logger.info("Deleted cache database at %s", db_path)
|
|
99
|
+
return True
|
|
100
|
+
_logger.debug("Cache database not found at %s, nothing to delete", db_path)
|
|
101
|
+
return False
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
"""
|
|
2
|
+
vulnerability_cache module.
|
|
3
|
+
|
|
4
|
+
Provides a SQLite-backed cache for vulnerability scan API responses.
|
|
5
|
+
Cache entries expire after a configurable number of days (default: 7).
|
|
6
|
+
|
|
7
|
+
:author: Ron Webb
|
|
8
|
+
:since: 1.0.0
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
import sqlite3
|
|
12
|
+
from datetime import datetime, timedelta
|
|
13
|
+
|
|
14
|
+
from ..util.logger import setup_logger
|
|
15
|
+
from .db import get_connection
|
|
16
|
+
|
|
17
|
+
__author__ = "Ron Webb"
|
|
18
|
+
__since__ = "1.0.0"
|
|
19
|
+
|
|
20
|
+
_logger = setup_logger(__name__)
|
|
21
|
+
|
|
22
|
+
_DEFAULT_TTL_DAYS = 7
|
|
23
|
+
|
|
24
|
+
_SELECT_SQL = """
|
|
25
|
+
SELECT payload, cached_at FROM vulnerability_cache
|
|
26
|
+
WHERE source = ? AND group_id = ? AND artifact_id = ? AND version = ?
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
_UPSERT_SQL = """
|
|
30
|
+
INSERT INTO vulnerability_cache(source, group_id, artifact_id, version, payload, cached_at)
|
|
31
|
+
VALUES (?, ?, ?, ?, ?, ?)
|
|
32
|
+
ON CONFLICT(source, group_id, artifact_id, version)
|
|
33
|
+
DO UPDATE SET payload = excluded.payload, cached_at = excluded.cached_at
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
_DELETE_SQL = """
|
|
37
|
+
DELETE FROM vulnerability_cache
|
|
38
|
+
WHERE source = ? AND group_id = ? AND artifact_id = ? AND version = ?
|
|
39
|
+
"""
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class VulnerabilityCache:
|
|
43
|
+
"""
|
|
44
|
+
SQLite-backed cache for raw vulnerability API payloads.
|
|
45
|
+
|
|
46
|
+
Each entry is keyed by ``(source, group_id, artifact_id, version)`` and stores
|
|
47
|
+
the raw JSON response payload string. Entries older than ``ttl_days`` are
|
|
48
|
+
treated as expired and will not be returned.
|
|
49
|
+
|
|
50
|
+
:author: Ron Webb
|
|
51
|
+
:since: 1.0.0
|
|
52
|
+
"""
|
|
53
|
+
|
|
54
|
+
def __init__(
|
|
55
|
+
self,
|
|
56
|
+
connection: sqlite3.Connection | None = None,
|
|
57
|
+
ttl_days: int = _DEFAULT_TTL_DAYS,
|
|
58
|
+
) -> None:
|
|
59
|
+
"""
|
|
60
|
+
Initialise the cache with an optional SQLite connection and TTL.
|
|
61
|
+
|
|
62
|
+
When *connection* is ``None`` the default on-disk database is used.
|
|
63
|
+
|
|
64
|
+
:author: Ron Webb
|
|
65
|
+
:since: 1.0.0
|
|
66
|
+
"""
|
|
67
|
+
self._conn = connection or get_connection()
|
|
68
|
+
self._ttl_days = ttl_days
|
|
69
|
+
|
|
70
|
+
def get(
|
|
71
|
+
self,
|
|
72
|
+
source: str,
|
|
73
|
+
group_id: str,
|
|
74
|
+
artifact_id: str,
|
|
75
|
+
version: str,
|
|
76
|
+
) -> str | None:
|
|
77
|
+
"""
|
|
78
|
+
Return the cached JSON payload for the given key, or ``None`` when there
|
|
79
|
+
is no valid (non-expired) entry.
|
|
80
|
+
|
|
81
|
+
:author: Ron Webb
|
|
82
|
+
:since: 1.0.0
|
|
83
|
+
"""
|
|
84
|
+
cursor = self._conn.execute(_SELECT_SQL, (source, group_id, artifact_id, version))
|
|
85
|
+
row = cursor.fetchone()
|
|
86
|
+
if row is None:
|
|
87
|
+
return None
|
|
88
|
+
payload, cached_at = row
|
|
89
|
+
if self._is_expired(cached_at):
|
|
90
|
+
with self._conn:
|
|
91
|
+
self._conn.execute(_DELETE_SQL, (source, group_id, artifact_id, version))
|
|
92
|
+
_logger.debug(
|
|
93
|
+
"Cache entry expired and deleted for %s/%s:%s@%s",
|
|
94
|
+
source,
|
|
95
|
+
group_id,
|
|
96
|
+
artifact_id,
|
|
97
|
+
version,
|
|
98
|
+
)
|
|
99
|
+
return None
|
|
100
|
+
_logger.debug(
|
|
101
|
+
"Cache hit for %s/%s:%s@%s", source, group_id, artifact_id, version
|
|
102
|
+
)
|
|
103
|
+
return payload
|
|
104
|
+
|
|
105
|
+
def put( # pylint: disable=too-many-arguments,too-many-positional-arguments
|
|
106
|
+
self,
|
|
107
|
+
source: str,
|
|
108
|
+
group_id: str,
|
|
109
|
+
artifact_id: str,
|
|
110
|
+
version: str,
|
|
111
|
+
payload: str,
|
|
112
|
+
) -> None:
|
|
113
|
+
"""
|
|
114
|
+
Insert or replace the cache entry for the given key.
|
|
115
|
+
|
|
116
|
+
*payload* must be a JSON string representing the raw API response.
|
|
117
|
+
|
|
118
|
+
:author: Ron Webb
|
|
119
|
+
:since: 1.0.0
|
|
120
|
+
"""
|
|
121
|
+
now = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
|
122
|
+
with self._conn:
|
|
123
|
+
self._conn.execute(
|
|
124
|
+
_UPSERT_SQL,
|
|
125
|
+
(source, group_id, artifact_id, version, payload, now),
|
|
126
|
+
)
|
|
127
|
+
_logger.debug(
|
|
128
|
+
"Cached payload for %s/%s:%s@%s", source, group_id, artifact_id, version
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
def close(self) -> None:
|
|
132
|
+
"""
|
|
133
|
+
Close the underlying SQLite connection.
|
|
134
|
+
|
|
135
|
+
Call this when the cache is no longer needed to release the database
|
|
136
|
+
file handle immediately. Callers that passed their own *connection* to
|
|
137
|
+
the constructor are responsible for not using the connection after calling
|
|
138
|
+
this method.
|
|
139
|
+
|
|
140
|
+
:author: Ron Webb
|
|
141
|
+
:since: 1.0.0
|
|
142
|
+
"""
|
|
143
|
+
self._conn.close()
|
|
144
|
+
|
|
145
|
+
def _is_expired(self, cached_at: str) -> bool:
|
|
146
|
+
"""
|
|
147
|
+
Return ``True`` when *cached_at* is older than the configured TTL.
|
|
148
|
+
|
|
149
|
+
:author: Ron Webb
|
|
150
|
+
:since: 1.0.0
|
|
151
|
+
"""
|
|
152
|
+
try:
|
|
153
|
+
entry_time = datetime.strptime(cached_at, "%Y-%m-%d %H:%M:%S")
|
|
154
|
+
except ValueError:
|
|
155
|
+
return True
|
|
156
|
+
return datetime.now() - entry_time > timedelta(days=self._ttl_days)
|