equilibrium 0.1.1 → 0.3.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/.ruby-version +1 -1
- data/CHANGELOG.md +29 -13
- data/README.md +97 -56
- data/lib/equilibrium/analyzer.rb +32 -50
- data/lib/equilibrium/canonical_version_mapper.rb +19 -0
- data/lib/equilibrium/catalog_builder.rb +31 -15
- data/lib/equilibrium/cli.rb +18 -181
- data/lib/equilibrium/commands/actual_command.rb +36 -0
- data/lib/equilibrium/commands/analyze_command.rb +49 -0
- data/lib/equilibrium/commands/catalog_command.rb +38 -0
- data/lib/equilibrium/commands/expected_command.rb +36 -0
- data/lib/equilibrium/commands/uncatalog_command.rb +42 -0
- data/lib/equilibrium/commands/version_command.rb +16 -0
- data/lib/equilibrium/mixins/error_handling.rb +34 -0
- data/lib/equilibrium/mixins/input_output.rb +40 -0
- data/lib/equilibrium/registry_client.rb +34 -45
- data/lib/equilibrium/repository_tags_service.rb +32 -0
- data/lib/equilibrium/repository_url_validator.rb +14 -0
- data/lib/equilibrium/schema_validator.rb +10 -2
- data/lib/equilibrium/schemas/analyzer_output.rb +34 -32
- data/lib/equilibrium/schemas/catalog.rb +16 -9
- data/lib/equilibrium/schemas/expected_actual.rb +1 -1
- data/lib/equilibrium/schemas/registry_api.rb +1 -1
- data/lib/equilibrium/summary_formatter.rb +11 -11
- data/lib/equilibrium/tag_data_builder.rb +16 -0
- data/lib/equilibrium/tag_processor.rb +24 -10
- data/lib/equilibrium/tag_sorter.rb +2 -16
- data/lib/equilibrium/tags_operation_service.rb +32 -0
- data/lib/equilibrium/version.rb +1 -1
- data/lib/equilibrium.rb +0 -1
- metadata +18 -10
- data/lib/equilibrium/semantic_version.rb +0 -24
- data/tmp/.gitkeep +0 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 2a837a7453d70ee952b97888dde397411eaa7b2e22ba7a9147caf07b75ccfc17
|
4
|
+
data.tar.gz: 7367d2d4aed721970a617a15aec9ba81cb4e16cbaf1151d57aec4cd4a5d564e3
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 0fcc623d3164a2c20afb658d1f164b3ee391622290db618d27e1d628c1144e1eb0f7434d6e1e29bead38bf47c45115edbe5748bf520bb5462898cf7c988aaa37
|
7
|
+
data.tar.gz: 35bcc231d9e5f55e80b344586365b93345738a8edfb3e681ff9bada813e9922358b20e96e04cdac48a9b2e353c732269d218d53870a9ba32495a96270e425151
|
data/.ruby-version
CHANGED
@@ -1 +1 @@
|
|
1
|
-
3.
|
1
|
+
3.4.5
|
data/CHANGELOG.md
CHANGED
@@ -5,22 +5,38 @@ All notable changes to this project will be documented in this file.
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
7
7
|
|
8
|
-
## [
|
8
|
+
## [Unreleased]
|
9
|
+
|
10
|
+
## [0.3.0] - 2025-08-26
|
9
11
|
|
10
12
|
### Added
|
11
|
-
-
|
12
|
-
|
13
|
-
|
13
|
+
- Docker distribution support
|
14
|
+
|
15
|
+
### Changed
|
16
|
+
- Use Ruby 3.4.5
|
17
|
+
- Unified tag structures across analyze command output
|
18
|
+
- Improved summary format for better readability and consistency
|
19
|
+
|
20
|
+
### Removed
|
21
|
+
- Remove remediation_plan field from analyze command output
|
22
|
+
|
23
|
+
## [0.2.0] - 2025-08-15
|
24
|
+
|
25
|
+
### Added
|
26
|
+
- Add `uncatalog` command for reverse catalog conversion
|
27
|
+
|
28
|
+
### Changed
|
29
|
+
- Catalog schema includes `repository_url` and `repository_name` at root level
|
30
|
+
|
31
|
+
### Fixed
|
32
|
+
- Fix changelog reference links
|
33
|
+
|
34
|
+
## [0.1.1] - 2025-08-06
|
14
35
|
|
15
36
|
### Fixed
|
16
37
|
- Preserve descending tag order in summary format output
|
17
38
|
- Consistent descending ordering for expected and actual outputs
|
18
39
|
|
19
|
-
### Changed
|
20
|
-
- Reorganized spec files to mirror lib directory structure
|
21
|
-
- Extracted TagSorter utility with comprehensive unit tests
|
22
|
-
- Enhanced RegistryClient with pagination analysis capabilities
|
23
|
-
|
24
40
|
## [0.1.0] - 2025-08-05
|
25
41
|
|
26
42
|
### Added
|
@@ -38,8 +54,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
38
54
|
- CI/CD pipeline with GitHub Actions
|
39
55
|
- Trusted publishing support for RubyGems
|
40
56
|
- Standard rake task integration
|
41
|
-
|
42
|
-
### Features
|
43
57
|
- **Tag Validation**: Validates equilibrium between mutable tags and semantic version tags
|
44
58
|
- **Registry Client**: Pure Ruby HTTP client for container registry API access
|
45
59
|
- **Tag Processing**: Computes expected mutable tags from semantic versions (latest, major, minor)
|
@@ -53,5 +67,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
53
67
|
- Detailed architecture overview and data flow diagrams
|
54
68
|
- Complete command reference and examples
|
55
69
|
|
56
|
-
[0.
|
57
|
-
[0.
|
70
|
+
[0.3.0]: https://github.com/TonyCTHsu/equilibrium/releases/tag/v0.3.0
|
71
|
+
[0.2.0]: https://github.com/TonyCTHsu/equilibrium/releases/tag/v0.2.0
|
72
|
+
[0.1.1]: https://github.com/TonyCTHsu/equilibrium/releases/tag/v0.1.1
|
73
|
+
[0.1.0]: https://github.com/TonyCTHsu/equilibrium/releases/tag/v0.1.0
|
data/README.md
CHANGED
@@ -15,7 +15,6 @@ A container image tool that validates equilibrium between mutable tags and seman
|
|
15
15
|
- [Quick Start](#quick-start)
|
16
16
|
- [Output Formats & Schemas](#output-formats--schemas)
|
17
17
|
- [Constraints](#constraints)
|
18
|
-
- [Examples](#examples)
|
19
18
|
- [License](#license)
|
20
19
|
|
21
20
|
## The Problem
|
@@ -54,19 +53,27 @@ flowchart LR
|
|
54
53
|
H --> I[Compare<br/>Expected vs<br/>Actual]
|
55
54
|
F --> I
|
56
55
|
|
57
|
-
I --> J[Analysis
|
56
|
+
I --> J[Analysis Report<br/>Missing: 1<br/>Mismatched: latest]
|
57
|
+
|
58
|
+
H --> K[catalog<br/>Command]
|
59
|
+
K --> L[Catalog Format<br/>External Integration]
|
60
|
+
L --> M[uncatalog<br/>Command]
|
61
|
+
M --> N[Back to Expected/<br/>Actual Format]
|
58
62
|
|
59
63
|
classDef source fill:#e1d5e7
|
60
64
|
classDef process fill:#fff2cc
|
61
65
|
classDef expected fill:#d5e8d4
|
62
66
|
classDef actual fill:#f8cecc
|
63
67
|
classDef result fill:#ffcccc
|
68
|
+
classDef catalog fill:#dae8fc
|
64
69
|
|
65
70
|
class A source
|
66
71
|
class C,D,G,I process
|
67
72
|
class E,H expected
|
68
73
|
class F actual
|
69
74
|
class J result
|
75
|
+
class K,M catalog
|
76
|
+
class L,N catalog
|
70
77
|
```
|
71
78
|
|
72
79
|
**Process Steps:**
|
@@ -74,7 +81,8 @@ flowchart LR
|
|
74
81
|
2. **Filter Semantic**: Extract only valid semantic version tags (MAJOR.MINOR.PATCH)
|
75
82
|
3. **Compute Expected**: Generate mutable tags based on semantic versions
|
76
83
|
4. **Fetch Actual**: Query registry for current mutable tag state
|
77
|
-
5. **Compare & Analyze**: Identify
|
84
|
+
5. **Compare & Analyze**: Identify missing, unexpected, and mismatched tags with unified structure
|
85
|
+
6. **Format Conversion**: Transform between expected/actual and catalog formats for external integration
|
78
86
|
|
79
87
|
## Installation
|
80
88
|
|
@@ -82,6 +90,13 @@ flowchart LR
|
|
82
90
|
gem install equilibrium
|
83
91
|
```
|
84
92
|
|
93
|
+
For containerized environments, use the Docker image:
|
94
|
+
|
95
|
+
```bash
|
96
|
+
# Pull the latest version
|
97
|
+
docker pull ghcr.io/tonycthsu/equilibrium:latest
|
98
|
+
```
|
99
|
+
|
85
100
|
*For other installation methods, see `equilibrium help`*
|
86
101
|
|
87
102
|
## Quick Start
|
@@ -94,10 +109,14 @@ equilibrium expected "$REPO"
|
|
94
109
|
# 2. Check what mutable tags actually exist
|
95
110
|
equilibrium actual "$REPO"
|
96
111
|
|
97
|
-
# 3. Compare and
|
112
|
+
# 3. Compare and analyze differences
|
98
113
|
equilibrium expected "$REPO" --format json > expected.json
|
99
114
|
equilibrium actual "$REPO" --format json > actual.json
|
100
115
|
equilibrium analyze --expected expected.json --actual actual.json
|
116
|
+
|
117
|
+
# 4. Convert to catalog format and back (Round trip)
|
118
|
+
equilibrium catalog expected.json | tee catalog.json
|
119
|
+
equilibrium uncatalog catalog.json | tee uncatalog.json
|
101
120
|
```
|
102
121
|
|
103
122
|
*For detailed command options, run `equilibrium help [command]`*
|
@@ -197,55 +216,66 @@ All commands output structured JSON following validated schemas (see [schemas](l
|
|
197
216
|
```json
|
198
217
|
{
|
199
218
|
"repository_url": "gcr.io/project/image",
|
219
|
+
"repository_name": "image",
|
200
220
|
"expected_count": 3,
|
201
221
|
"actual_count": 2,
|
202
222
|
"missing_tags": {
|
203
|
-
"1.2":
|
223
|
+
"1.2": {
|
224
|
+
"expected": "sha256:abc123ef456789012345678901234567890123456789012345678901234567890",
|
225
|
+
"actual": ""
|
226
|
+
}
|
227
|
+
},
|
228
|
+
"unexpected_tags": {
|
229
|
+
"dev": {
|
230
|
+
"expected": "",
|
231
|
+
"actual": "sha256:xyz789ab123456789012345678901234567890123456789012345678901234"
|
232
|
+
}
|
204
233
|
},
|
205
|
-
"unexpected_tags": {},
|
206
234
|
"mismatched_tags": {
|
207
235
|
"latest": {
|
208
236
|
"expected": "sha256:def456ab789123456789012345678901234567890123456789012345678901",
|
209
237
|
"actual": "sha256:old123ef456789012345678901234567890123456789012345678901234567890"
|
210
238
|
}
|
211
239
|
},
|
212
|
-
"status": "missing_tags"
|
213
|
-
"remediation_plan": [
|
214
|
-
{
|
215
|
-
"action": "create_tag",
|
216
|
-
"tag": "1.2",
|
217
|
-
"digest": "sha256:abc123ef456789012345678901234567890123456789012345678901234567890",
|
218
|
-
"command": "gcloud container images add-tag gcr.io/project/image@sha256:abc123... gcr.io/project/image:1.2"
|
219
|
-
},
|
220
|
-
{
|
221
|
-
"action": "update_tag",
|
222
|
-
"tag": "latest",
|
223
|
-
"old_digest": "sha256:old123ef456789012345678901234567890123456789012345678901234567890",
|
224
|
-
"new_digest": "sha256:def456ab789123456789012345678901234567890123456789012345678901",
|
225
|
-
"command": "gcloud container images add-tag gcr.io/project/image@sha256:def456... gcr.io/project/image:latest"
|
226
|
-
}
|
227
|
-
]
|
240
|
+
"status": "missing_tags"
|
228
241
|
}
|
229
242
|
```
|
230
243
|
|
244
|
+
#### Unified Tag Structure
|
245
|
+
|
246
|
+
All tag analysis results (`missing_tags`, `unexpected_tags`, `mismatched_tags`) use a consistent structure:
|
247
|
+
|
248
|
+
- **`missing_tags`**: Tags that should exist but don't
|
249
|
+
- `expected`: The SHA256 digest the tag should point to
|
250
|
+
- `actual`: Empty string (tag doesn't exist)
|
251
|
+
|
252
|
+
- **`unexpected_tags`**: Tags that exist but shouldn't
|
253
|
+
- `expected`: Empty string (tag shouldn't exist)
|
254
|
+
- `actual`: The SHA256 digest the tag currently points to
|
255
|
+
|
256
|
+
- **`mismatched_tags`**: Tags that exist but point to wrong digest
|
257
|
+
- `expected`: The SHA256 digest the tag should point to
|
258
|
+
- `actual`: The SHA256 digest the tag currently points to
|
259
|
+
|
260
|
+
This unified structure makes programmatic processing easier and provides clear semantics about what was expected versus what was actually found.
|
261
|
+
|
231
262
|
**Catalog Command** ([schema](lib/equilibrium/schemas/catalog.rb)):
|
232
263
|
```json
|
233
264
|
{
|
265
|
+
"repository_url": "gcr.io/project/image",
|
266
|
+
"repository_name": "image",
|
234
267
|
"images": [
|
235
268
|
{
|
236
|
-
"name": "image",
|
237
269
|
"tag": "latest",
|
238
270
|
"digest": "sha256:5fcfe7ac14f6eeb0fe086ac7021d013d764af573b8c2d98113abf26b4d09b58c",
|
239
271
|
"canonical_version": "1.2.3"
|
240
272
|
},
|
241
273
|
{
|
242
|
-
"name": "image",
|
243
274
|
"tag": "1",
|
244
275
|
"digest": "sha256:5fcfe7ac14f6eeb0fe086ac7021d013d764af573b8c2d98113abf26b4d09b58c",
|
245
276
|
"canonical_version": "1.2.3"
|
246
277
|
},
|
247
278
|
{
|
248
|
-
"name": "image",
|
249
279
|
"tag": "1.2",
|
250
280
|
"digest": "sha256:5fcfe7ac14f6eeb0fe086ac7021d013d764af573b8c2d98113abf26b4d09b58c",
|
251
281
|
"canonical_version": "1.2.3"
|
@@ -254,6 +284,8 @@ All commands output structured JSON following validated schemas (see [schemas](l
|
|
254
284
|
}
|
255
285
|
```
|
256
286
|
|
287
|
+
**Uncatalog Command**: Converts catalog format back to expected/actual format (same schema as expected/actual commands).
|
288
|
+
|
257
289
|
### Summary Format
|
258
290
|
Human-readable table format for quick visual inspection.
|
259
291
|
|
@@ -263,50 +295,59 @@ Human-readable table format for quick visual inspection.
|
|
263
295
|
- **Tag Format**: Only processes semantic version tags (MAJOR.MINOR.PATCH)
|
264
296
|
- **URL Format**: Requires full repository URLs: `[REGISTRY_HOST]/[NAMESPACE]/[REPOSITORY]`
|
265
297
|
|
266
|
-
|
298
|
+
### Local Image Build
|
299
|
+
|
300
|
+
To build the Docker image locally, you need the gem artifact in the `pkg/` directory:
|
267
301
|
|
268
|
-
### Example 1: Perfect Equilibrium
|
269
302
|
```bash
|
270
|
-
|
271
|
-
#
|
303
|
+
# Build the gem using the same method as CI
|
304
|
+
bundle exec rake clobber # Clean previous builds
|
305
|
+
bundle exec rake build # Rebuild gem
|
272
306
|
|
273
|
-
|
274
|
-
|
307
|
+
# Build image
|
308
|
+
podman build -f docker/Dockerfile -t equilibrium:local .
|
275
309
|
|
276
|
-
|
277
|
-
|
310
|
+
# Test the local build
|
311
|
+
podman run --rm localhost/equilibrium:local equilibrium version
|
278
312
|
```
|
279
313
|
|
280
|
-
|
281
|
-
```bash
|
282
|
-
$ equilibrium expected gcr.io/project/myapp
|
283
|
-
# Expected: latest→2.1.0, 1→1.5.3, 2→2.1.0, 2.1→2.1.0
|
314
|
+
**Note**: The Dockerfile expects the gem artifact in `pkg/*.gem`. Using `bundle exec rake build` ensures you build the gem exactly the same way as the CI pipeline.
|
284
315
|
|
285
|
-
|
286
|
-
# Actual: latest→1.5.3, 1→1.5.3 (missing: 2, 2.1)
|
316
|
+
## Development
|
287
317
|
|
288
|
-
|
289
|
-
|
290
|
-
|
318
|
+
### Prerequisites
|
319
|
+
|
320
|
+
- **Ruby** (>= 3.0.0) with Bundler
|
321
|
+
- **No authentication required** for public Google Container Registry (GCR) access
|
322
|
+
- **Pure Ruby implementation** - uses only Ruby standard library (Net::HTTP) for HTTP requests
|
323
|
+
|
324
|
+
### Code Quality
|
325
|
+
|
326
|
+
```bash
|
327
|
+
# Ruby linting
|
328
|
+
bundle exec standardrb
|
329
|
+
bundle exec standardrb --fix
|
330
|
+
|
331
|
+
# Run tests
|
332
|
+
bundle exec rspec
|
291
333
|
```
|
292
334
|
|
293
|
-
###
|
335
|
+
### Release Process
|
336
|
+
|
337
|
+
To create a new release:
|
338
|
+
|
294
339
|
```bash
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
# Daily equilibrium check
|
299
|
-
equilibrium expected "$REPO" > expected.json
|
300
|
-
equilibrium actual "$REPO" > actual.json
|
301
|
-
equilibrium analyze --expected expected.json --actual actual.json --format json > report.json
|
302
|
-
|
303
|
-
# Alert if out of equilibrium
|
304
|
-
if grep -q '"status": "out_of_equilibrium"' report.json; then
|
305
|
-
echo "⚠️ Repository $REPO is out of equilibrium!"
|
306
|
-
equilibrium analyze --expected expected.json --actual actual.json
|
307
|
-
fi
|
340
|
+
# Update version in lib/equilibrium/version.rb, then:
|
341
|
+
./bin/release
|
308
342
|
```
|
309
343
|
|
344
|
+
The script automatically:
|
345
|
+
1. Reads the version from `equilibrium.gemspec`
|
346
|
+
2. Creates a git tag with `v` prefix (e.g., `v0.1.1`)
|
347
|
+
3. Pushes the tag to trigger the automated release workflow
|
348
|
+
|
349
|
+
The GitHub Actions workflow validates that the tag version matches the gemspec version before publishing to RubyGems and GitHub Packages.
|
350
|
+
|
310
351
|
## License
|
311
352
|
|
312
353
|
MIT License - see [LICENSE](LICENSE) file for details.
|
data/lib/equilibrium/analyzer.rb
CHANGED
@@ -4,84 +4,66 @@ require "json"
|
|
4
4
|
|
5
5
|
module Equilibrium
|
6
6
|
class Analyzer
|
7
|
-
def
|
7
|
+
def self.analyze(expected_data, actual_data)
|
8
|
+
new.analyze(expected_data, actual_data)
|
8
9
|
end
|
9
10
|
|
10
|
-
# Analyzes validated expected/actual data in schema format
|
11
11
|
def analyze(expected_data, actual_data)
|
12
12
|
# Extract digests from validated schema format
|
13
13
|
expected_tags = expected_data["digests"]
|
14
14
|
actual_tags = actual_data["digests"]
|
15
15
|
|
16
|
-
# Extract and validate repository
|
16
|
+
# Extract and validate repository names match
|
17
|
+
expected_name = expected_data["repository_name"]
|
18
|
+
actual_name = actual_data["repository_name"]
|
17
19
|
expected_url = expected_data["repository_url"]
|
18
20
|
actual_url = actual_data["repository_url"]
|
19
21
|
|
20
|
-
if
|
21
|
-
raise ArgumentError, "Repository
|
22
|
+
if expected_name != actual_name
|
23
|
+
raise ArgumentError, "Repository names do not match: expected '#{expected_name}', actual '#{actual_name}'"
|
22
24
|
end
|
23
25
|
|
24
|
-
|
26
|
+
final_repository_name = expected_name
|
27
|
+
final_repository_url = expected_url || actual_url
|
25
28
|
|
26
|
-
|
29
|
+
{
|
27
30
|
repository_url: final_repository_url,
|
31
|
+
repository_name: final_repository_name,
|
28
32
|
expected_count: expected_tags.size,
|
29
33
|
actual_count: actual_tags.size,
|
30
34
|
missing_tags: find_missing_tags(expected_tags, actual_tags),
|
31
35
|
unexpected_tags: find_unexpected_tags(expected_tags, actual_tags),
|
32
36
|
mismatched_tags: find_mismatched_tags(expected_tags, actual_tags),
|
33
37
|
status: determine_status(expected_tags, actual_tags)
|
34
|
-
}
|
35
|
-
|
36
|
-
# Add remediation plan for JSON format
|
37
|
-
analysis[:remediation_plan] = generate_remediation_plan(analysis, final_repository_url)
|
38
|
-
analysis
|
39
|
-
end
|
40
|
-
|
41
|
-
private
|
42
|
-
|
43
|
-
def generate_remediation_plan(analysis, repository_url)
|
44
|
-
plan = []
|
45
|
-
|
46
|
-
analysis[:missing_tags].each do |tag, digest|
|
47
|
-
plan << {
|
48
|
-
action: "create_tag",
|
49
|
-
tag: tag,
|
50
|
-
digest: digest,
|
51
|
-
command: "gcloud container images add-tag #{repository_url}@#{digest} #{repository_url}:#{tag}"
|
52
|
-
}
|
53
|
-
end
|
54
|
-
|
55
|
-
analysis[:mismatched_tags].each do |tag, data|
|
56
|
-
plan << {
|
57
|
-
action: "update_tag",
|
58
|
-
tag: tag,
|
59
|
-
old_digest: data[:actual],
|
60
|
-
new_digest: data[:expected],
|
61
|
-
command: "gcloud container images add-tag #{repository_url}@#{data[:expected]} #{repository_url}:#{tag}"
|
62
|
-
}
|
63
|
-
end
|
64
|
-
|
65
|
-
analysis[:unexpected_tags].each do |tag, digest|
|
66
|
-
plan << {
|
67
|
-
action: "remove_tag",
|
68
|
-
tag: tag,
|
69
|
-
digest: digest,
|
70
|
-
command: "gcloud container images untag #{repository_url}:#{tag}"
|
71
|
-
}
|
72
|
-
end
|
73
|
-
|
74
|
-
plan
|
38
|
+
}.compact
|
75
39
|
end
|
76
40
|
|
77
41
|
private
|
78
42
|
|
79
43
|
def find_missing_tags(expected, actual)
|
80
|
-
|
44
|
+
missing = {}
|
45
|
+
expected.each do |tag, digest|
|
46
|
+
unless actual.key?(tag)
|
47
|
+
missing[tag] = {
|
48
|
+
expected: digest,
|
49
|
+
actual: ""
|
50
|
+
}
|
51
|
+
end
|
52
|
+
end
|
53
|
+
missing
|
81
54
|
end
|
82
55
|
|
83
56
|
def find_unexpected_tags(expected, actual)
|
84
|
-
|
57
|
+
unexpected = {}
|
58
|
+
actual.each do |tag, digest|
|
59
|
+
unless expected.key?(tag)
|
60
|
+
unexpected[tag] = {
|
61
|
+
expected: "",
|
62
|
+
actual: digest
|
63
|
+
}
|
64
|
+
end
|
65
|
+
end
|
66
|
+
unexpected
|
85
67
|
end
|
86
68
|
|
87
69
|
def find_mismatched_tags(expected, actual)
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Equilibrium
|
4
|
+
class CanonicalVersionMapper
|
5
|
+
def self.map_to_canonical_versions(mutable_tags, semantic_tags)
|
6
|
+
canonical_versions = {}
|
7
|
+
|
8
|
+
mutable_tags.each do |mutable_tag, m_digest|
|
9
|
+
# Find semantic tag with same digest, raise if not found
|
10
|
+
canonical_version = semantic_tags.key(m_digest) ||
|
11
|
+
(raise "No semantic tag found with digest #{m_digest} for mutable tag '#{mutable_tag}'")
|
12
|
+
|
13
|
+
canonical_versions[mutable_tag] = canonical_version
|
14
|
+
end
|
15
|
+
|
16
|
+
canonical_versions
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -1,45 +1,61 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require "json"
|
4
|
-
require "json_schemer"
|
5
|
-
require_relative "schemas/catalog"
|
6
4
|
|
7
5
|
module Equilibrium
|
8
6
|
class CatalogBuilder
|
9
7
|
class Error < StandardError; end
|
10
8
|
|
11
|
-
def build_catalog(data)
|
12
|
-
# Extract repository name, digests, and canonical versions from the validated data structure
|
9
|
+
def self.build_catalog(data)
|
13
10
|
repository_name = data["repository_name"]
|
11
|
+
repository_url = data["repository_url"]
|
14
12
|
digests = data["digests"]
|
15
13
|
canonical_versions = data["canonical_versions"]
|
16
14
|
|
17
15
|
images = digests.map do |tag, digest|
|
18
16
|
{
|
19
|
-
"name" => repository_name,
|
20
17
|
"tag" => tag,
|
21
18
|
"digest" => digest,
|
22
19
|
"canonical_version" => canonical_versions[tag]
|
23
20
|
}
|
24
21
|
end
|
25
22
|
|
26
|
-
|
23
|
+
{
|
24
|
+
"repository_url" => repository_url,
|
25
|
+
"repository_name" => repository_name,
|
27
26
|
"images" => images
|
28
27
|
}
|
29
|
-
|
30
|
-
validate_catalog(catalog)
|
31
|
-
catalog
|
32
28
|
end
|
33
29
|
|
34
|
-
|
30
|
+
def self.reverse_catalog(catalog_data)
|
31
|
+
images = catalog_data["images"]
|
32
|
+
repository_url = catalog_data["repository_url"]
|
33
|
+
repository_name = catalog_data["repository_name"]
|
34
|
+
|
35
|
+
if images.nil? || images.empty?
|
36
|
+
return {
|
37
|
+
"repository_url" => repository_url || "",
|
38
|
+
"repository_name" => repository_name || "",
|
39
|
+
"digests" => {},
|
40
|
+
"canonical_versions" => {}
|
41
|
+
}
|
42
|
+
end
|
35
43
|
|
36
|
-
|
37
|
-
|
38
|
-
errors = schemer.validate(catalog).to_a
|
44
|
+
digests = {}
|
45
|
+
canonical_versions = {}
|
39
46
|
|
40
|
-
|
41
|
-
|
47
|
+
images.each do |image|
|
48
|
+
tag = image["tag"]
|
49
|
+
digests[tag] = image["digest"]
|
50
|
+
canonical_versions[tag] = image["canonical_version"]
|
42
51
|
end
|
52
|
+
|
53
|
+
{
|
54
|
+
"repository_url" => repository_url,
|
55
|
+
"repository_name" => repository_name,
|
56
|
+
"digests" => digests,
|
57
|
+
"canonical_versions" => canonical_versions
|
58
|
+
}
|
43
59
|
end
|
44
60
|
end
|
45
61
|
end
|