equilibrium 0.1.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.
@@ -0,0 +1,116 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "thor"
4
+
5
+ module Equilibrium
6
+ class SummaryFormatter
7
+ include Thor::Shell
8
+
9
+ def initialize
10
+ # Initialize Thor::Shell methods with empty arguments
11
+ self.shell = Thor::Shell::Basic.new
12
+ end
13
+
14
+ def print_analysis_summary(analysis)
15
+ say "Repository URL: #{analysis[:repository_url]}"
16
+ say ""
17
+
18
+ # Status overview table
19
+ status_color = (analysis[:status] == "perfect") ? :green : :yellow
20
+ status_symbol = (analysis[:status] == "perfect") ? "✓" : "⚠"
21
+
22
+ overview_data = [
23
+ ["Metric", "Count"],
24
+ ["Expected tags", analysis[:expected_count].to_s],
25
+ ["Actual tags", analysis[:actual_count].to_s],
26
+ ["Missing tags", analysis[:missing_tags].size.to_s],
27
+ ["Mismatched tags", analysis[:mismatched_tags].size.to_s],
28
+ ["Unexpected tags", analysis[:unexpected_tags].size.to_s]
29
+ ]
30
+
31
+ say "Analysis Overview:"
32
+ print_table(overview_data, borders: true)
33
+ say ""
34
+
35
+ say "#{status_symbol} Status: #{analysis[:status].upcase.tr("_", " ")}", status_color
36
+ say ""
37
+
38
+ # Show specific issue sections
39
+ has_issues = false
40
+
41
+ # Missing tags section
42
+ if analysis[:missing_tags].any?
43
+ has_issues = true
44
+ say "Missing Tags (should be created):"
45
+ missing_table = [["Tag", "Should Point To"]]
46
+ analysis[:missing_tags].each do |tag, digest|
47
+ short_digest = digest ? digest.split(":").last[0..11] : "unknown"
48
+ missing_table << [tag, short_digest]
49
+ end
50
+ print_table(missing_table, borders: true)
51
+ say ""
52
+ end
53
+
54
+ # Mismatched tags section
55
+ if analysis[:mismatched_tags].any?
56
+ has_issues = true
57
+ say "Mismatched Tags (pointing to wrong version):"
58
+ mismatched_table = [["Tag", "Expected", "Actual"]]
59
+ analysis[:mismatched_tags].each do |tag, details|
60
+ if details.is_a?(Hash)
61
+ expected = details[:expected] ? details[:expected].split(":").last[0..11] : "unknown"
62
+ actual = details[:actual] ? details[:actual].split(":").last[0..11] : "unknown"
63
+ else
64
+ expected = details ? details.split(":").last[0..11] : "unknown"
65
+ actual = "unknown"
66
+ end
67
+ mismatched_table << [tag, expected, actual]
68
+ end
69
+ print_table(mismatched_table, borders: true)
70
+ say ""
71
+ end
72
+
73
+ # Unexpected tags section
74
+ if analysis[:unexpected_tags].any?
75
+ has_issues = true
76
+ say "Unexpected Tags (should be removed):"
77
+ unexpected_table = [["Tag", "Currently Points To"]]
78
+ analysis[:unexpected_tags].each do |tag, digest|
79
+ short_digest = digest ? digest.split(":").last[0..11] : "unknown"
80
+ unexpected_table << [tag, short_digest]
81
+ end
82
+ print_table(unexpected_table, borders: true)
83
+ say ""
84
+ end
85
+
86
+ if has_issues
87
+ say "To see detailed remediation commands, use:"
88
+ say " equilibrium analyze --expected expected.json --actual actual.json --format=json | jq '.remediation_plan'"
89
+ else
90
+ say "✓ Registry is in perfect equilibrium!", :green
91
+ end
92
+ end
93
+
94
+ def print_tags_summary(output, type)
95
+ say "Repository: #{output["repository_name"]}"
96
+ say "URL: #{output["repository_url"]}"
97
+ say ""
98
+
99
+ mutable_tags = output["digests"]
100
+ canonical_versions = output["canonical_versions"]
101
+
102
+ say "#{type.capitalize} mutable tags (#{mutable_tags.size}):"
103
+ say ""
104
+
105
+ # Create table data: [["Tag", "Version", "Digest"]]
106
+ table_data = [["Tag", "Version", "Digest"]]
107
+ mutable_tags.keys.sort.each do |tag|
108
+ canonical_version = canonical_versions[tag]
109
+ digest = mutable_tags[tag]
110
+ table_data << [tag, canonical_version, digest]
111
+ end
112
+
113
+ print_table(table_data, borders: true)
114
+ end
115
+ end
116
+ end
@@ -0,0 +1,84 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+
5
+ module Equilibrium
6
+ class TagProcessor
7
+ def self.compute_virtual_tags(semantic_tags)
8
+ new.compute_virtual_tags(semantic_tags)
9
+ end
10
+
11
+ def compute_virtual_tags(semantic_tags)
12
+ return {"digests" => {}, "canonical_versions" => {}} if semantic_tags.empty?
13
+
14
+ latest_version = nil
15
+ major_versions = {}
16
+ minor_versions = {}
17
+
18
+ # Single pass to find all maximums
19
+ semantic_tags.keys.each do |version_str|
20
+ version = Gem::Version.new(version_str)
21
+
22
+ # Track overall latest
23
+ latest_version = version if !latest_version || version > latest_version
24
+
25
+ # Track latest for each major
26
+ major = version.segments[0]
27
+ major_versions[major] = version if !major_versions[major] || version > major_versions[major]
28
+
29
+ # Track latest for each minor
30
+ major_minor = "#{version.segments[0]}.#{version.segments[1]}"
31
+ minor_versions[major_minor] = version if !minor_versions[major_minor] || version > minor_versions[major_minor]
32
+ end
33
+
34
+ # Build result with both digest and canonical mappings
35
+ digests = {}
36
+ canonical_versions = {}
37
+
38
+ if latest_version
39
+ digests["latest"] = semantic_tags[latest_version.to_s]
40
+ canonical_versions["latest"] = latest_version.to_s
41
+ end
42
+
43
+ major_versions.each do |_, v|
44
+ tag = v.segments[0].to_s
45
+ digests[tag] = semantic_tags[v.to_s]
46
+ canonical_versions[tag] = v.to_s
47
+ end
48
+
49
+ minor_versions.each do |_, v|
50
+ tag = "#{v.segments[0]}.#{v.segments[1]}"
51
+ digests[tag] = semantic_tags[v.to_s]
52
+ canonical_versions[tag] = v.to_s
53
+ end
54
+
55
+ {"digests" => digests, "canonical_versions" => canonical_versions}
56
+ end
57
+
58
+ def filter_semantic_tags(all_tags)
59
+ # Filter semantic tags (canonical_tags.json): exact major.minor.patch format
60
+ filtered = all_tags.select { |tag, _| semantic_version?(tag) }
61
+ # Sort by key in reverse order (matching original jq: sort_by(.key) | reverse)
62
+ filtered.sort_by { |tag, _| tag }.reverse.to_h
63
+ end
64
+
65
+ def filter_mutable_tags(all_tags)
66
+ # Filter mutable tags (actual_tags.json): latest, digits, or major.minor format
67
+ filtered = all_tags.select { |tag, _| mutable_tag?(tag) }
68
+ # Sort by key in reverse order (matching original jq: sort_by(.key) | reverse)
69
+ filtered.sort_by { |tag, _| tag }.reverse.to_h
70
+ end
71
+
72
+ private
73
+
74
+ def semantic_version?(tag)
75
+ SemanticVersion.valid?(tag)
76
+ end
77
+
78
+ def mutable_tag?(tag)
79
+ tag.match?(/^latest$/) ||
80
+ tag.match?(/^[0-9]+$/) ||
81
+ tag.match?(/^[0-9]+\.[0-9]+$/)
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Equilibrium
4
+ VERSION = "0.1.0"
5
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "equilibrium/version"
4
+ require_relative "equilibrium/semantic_version"
5
+ require_relative "equilibrium/registry_client"
6
+ require_relative "equilibrium/tag_processor"
7
+ require_relative "equilibrium/catalog_builder"
8
+ require_relative "equilibrium/analyzer"
9
+ require_relative "equilibrium/schemas/expected_actual"
10
+
11
+ module Equilibrium
12
+ class Error < StandardError; end
13
+ end
data/tmp/.gitkeep ADDED
@@ -0,0 +1,2 @@
1
+ # This file ensures the tmp/ directory is tracked by git
2
+ # while keeping all other contents ignored via .gitignore
metadata ADDED
@@ -0,0 +1,113 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: equilibrium
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Tony Hsu
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2025-08-05 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: thor
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: json
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '2.0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '2.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: json_schemer
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '2.0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '2.0'
55
+ description: Validates equilibrium between mutable tags and semantic version tags
56
+ in container registries
57
+ email:
58
+ - tonyc.t.hsu@gmail.com
59
+ executables:
60
+ - equilibrium
61
+ extensions: []
62
+ extra_rdoc_files: []
63
+ files:
64
+ - ".rspec"
65
+ - ".ruby-version"
66
+ - CHANGELOG.md
67
+ - LICENSE
68
+ - README.md
69
+ - Rakefile
70
+ - equilibrium
71
+ - exe/equilibrium
72
+ - lib/equilibrium.rb
73
+ - lib/equilibrium/analyzer.rb
74
+ - lib/equilibrium/catalog_builder.rb
75
+ - lib/equilibrium/cli.rb
76
+ - lib/equilibrium/registry_client.rb
77
+ - lib/equilibrium/schema_validator.rb
78
+ - lib/equilibrium/schemas/analyzer_output.rb
79
+ - lib/equilibrium/schemas/catalog.rb
80
+ - lib/equilibrium/schemas/expected_actual.rb
81
+ - lib/equilibrium/schemas/registry_api.rb
82
+ - lib/equilibrium/semantic_version.rb
83
+ - lib/equilibrium/summary_formatter.rb
84
+ - lib/equilibrium/tag_processor.rb
85
+ - lib/equilibrium/version.rb
86
+ - tmp/.gitkeep
87
+ homepage: https://github.com/TonyCTHsu/equilibrium
88
+ licenses:
89
+ - MIT
90
+ metadata:
91
+ homepage_uri: https://github.com/TonyCTHsu/equilibrium
92
+ source_code_uri: https://github.com/TonyCTHsu/equilibrium
93
+ changelog_uri: https://github.com/TonyCTHsu/equilibrium/blob/master/CHANGELOG.md
94
+ post_install_message:
95
+ rdoc_options: []
96
+ require_paths:
97
+ - lib
98
+ required_ruby_version: !ruby/object:Gem::Requirement
99
+ requirements:
100
+ - - ">="
101
+ - !ruby/object:Gem::Version
102
+ version: 3.0.0
103
+ required_rubygems_version: !ruby/object:Gem::Requirement
104
+ requirements:
105
+ - - ">="
106
+ - !ruby/object:Gem::Version
107
+ version: '0'
108
+ requirements: []
109
+ rubygems_version: 3.4.19
110
+ signing_key:
111
+ specification_version: 4
112
+ summary: Container image tag validation tool
113
+ test_files: []