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.
Files changed (34) hide show
  1. checksums.yaml +4 -4
  2. data/.ruby-version +1 -1
  3. data/CHANGELOG.md +29 -13
  4. data/README.md +97 -56
  5. data/lib/equilibrium/analyzer.rb +32 -50
  6. data/lib/equilibrium/canonical_version_mapper.rb +19 -0
  7. data/lib/equilibrium/catalog_builder.rb +31 -15
  8. data/lib/equilibrium/cli.rb +18 -181
  9. data/lib/equilibrium/commands/actual_command.rb +36 -0
  10. data/lib/equilibrium/commands/analyze_command.rb +49 -0
  11. data/lib/equilibrium/commands/catalog_command.rb +38 -0
  12. data/lib/equilibrium/commands/expected_command.rb +36 -0
  13. data/lib/equilibrium/commands/uncatalog_command.rb +42 -0
  14. data/lib/equilibrium/commands/version_command.rb +16 -0
  15. data/lib/equilibrium/mixins/error_handling.rb +34 -0
  16. data/lib/equilibrium/mixins/input_output.rb +40 -0
  17. data/lib/equilibrium/registry_client.rb +34 -45
  18. data/lib/equilibrium/repository_tags_service.rb +32 -0
  19. data/lib/equilibrium/repository_url_validator.rb +14 -0
  20. data/lib/equilibrium/schema_validator.rb +10 -2
  21. data/lib/equilibrium/schemas/analyzer_output.rb +34 -32
  22. data/lib/equilibrium/schemas/catalog.rb +16 -9
  23. data/lib/equilibrium/schemas/expected_actual.rb +1 -1
  24. data/lib/equilibrium/schemas/registry_api.rb +1 -1
  25. data/lib/equilibrium/summary_formatter.rb +11 -11
  26. data/lib/equilibrium/tag_data_builder.rb +16 -0
  27. data/lib/equilibrium/tag_processor.rb +24 -10
  28. data/lib/equilibrium/tag_sorter.rb +2 -16
  29. data/lib/equilibrium/tags_operation_service.rb +32 -0
  30. data/lib/equilibrium/version.rb +1 -1
  31. data/lib/equilibrium.rb +0 -1
  32. metadata +18 -10
  33. data/lib/equilibrium/semantic_version.rb +0 -24
  34. data/tmp/.gitkeep +0 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d710d1c2fea441e38c5b328c036506115b7b80ade1aae20ffda4badbf9c72b76
4
- data.tar.gz: c99ba87639818a92306c31c2764a604238a98607d4d655b63e6536c32b64af67
3
+ metadata.gz: 2a837a7453d70ee952b97888dde397411eaa7b2e22ba7a9147caf07b75ccfc17
4
+ data.tar.gz: 7367d2d4aed721970a617a15aec9ba81cb4e16cbaf1151d57aec4cd4a5d564e3
5
5
  SHA512:
6
- metadata.gz: d5ccfd2ac6a95cf83efeed1dc59b27e1c474f9211e6155da8ae35938bc9a4316c002509e9fb60757403c3130359b1f854a0aaf84bfd9c470caacdaa5cb8c9f14
7
- data.tar.gz: 76445a5a0e8e0f2e0a1f347394b44700a9e65fd2b3d23eaad0d8c1f387d54619aad184d9aa2fa50b6286660e140221cfef8e03def80768cbf5b5421e7aa61e2e
6
+ metadata.gz: 0fcc623d3164a2c20afb658d1f164b3ee391622290db618d27e1d628c1144e1eb0f7434d6e1e29bead38bf47c45115edbe5748bf520bb5462898cf7c988aaa37
7
+ data.tar.gz: 35bcc231d9e5f55e80b344586365b93345738a8edfb3e681ff9bada813e9922358b20e96e04cdac48a9b2e353c732269d218d53870a9ba32495a96270e425151
data/.ruby-version CHANGED
@@ -1 +1 @@
1
- 3.2.8
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
- ## [0.1.1] - 2025-08-06
8
+ ## [Unreleased]
9
+
10
+ ## [0.3.0] - 2025-08-26
9
11
 
10
12
  ### Added
11
- - GitHub Packages publishing support in release workflow
12
- - Draft mode for GitHub releases requiring manual review
13
- - Enhanced release automation with artifact attachment
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.1.1]: https://github.com/DataDog/equilibrium/releases/tag/v0.1.1
57
- [0.1.0]: https://github.com/DataDog/equilibrium/releases/tag/v0.1.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 &<br/>Remediation<br/>Update: latest]
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 mismatches and generate remediation plan
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 get remediation plan
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": "sha256:abc123ef456789012345678901234567890123456789012345678901234567890"
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
- ## Examples
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
- $ equilibrium expected gcr.io/google-containers/pause
271
- # Shows: latest→3.9, 3→3.9, 3.9→3.9
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
- $ equilibrium actual gcr.io/google-containers/pause
274
- # Shows: latest→3.9, 3→3.9, 3.9→3.9
307
+ # Build image
308
+ podman build -f docker/Dockerfile -t equilibrium:local .
275
309
 
276
- $ equilibrium analyze --expected expected.json --actual actual.json
277
- # Status: in_equilibrium
310
+ # Test the local build
311
+ podman run --rm localhost/equilibrium:local equilibrium version
278
312
  ```
279
313
 
280
- ### Example 2: Out of Equilibrium
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
- $ equilibrium actual gcr.io/project/myapp
286
- # Actual: latest→1.5.3, 1→1.5.3 (missing: 2, 2.1)
316
+ ## Development
287
317
 
288
- $ equilibrium analyze --expected expected.json --actual actual.json
289
- # Status: ❌ out_of_equilibrium
290
- # Remediation: Create tags: 2→2.1.0, 2.1→2.1.0; Update: latest→2.1.0
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
- ### Example 3: Automation Pipeline
335
+ ### Release Process
336
+
337
+ To create a new release:
338
+
294
339
  ```bash
295
- #!/bin/bash
296
- REPO="gcr.io/project/image"
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.
@@ -4,84 +4,66 @@ require "json"
4
4
 
5
5
  module Equilibrium
6
6
  class Analyzer
7
- def initialize
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 URLs match
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 expected_url != actual_url
21
- raise ArgumentError, "Repository URLs do not match: expected '#{expected_url}', actual '#{actual_url}'"
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
- final_repository_url = expected_url
26
+ final_repository_name = expected_name
27
+ final_repository_url = expected_url || actual_url
25
28
 
26
- analysis = {
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
- expected.reject { |tag, digest| actual.key?(tag) }
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
- actual.reject { |tag, _| expected.key?(tag) }
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
- catalog = {
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
- private
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
- def validate_catalog(catalog)
37
- schemer = JSONSchemer.schema(Equilibrium::Schemas::CATALOG)
38
- errors = schemer.validate(catalog).to_a
44
+ digests = {}
45
+ canonical_versions = {}
39
46
 
40
- unless errors.empty?
41
- raise Error, "Catalog validation failed: #{errors.map(&:to_s).join(", ")}"
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