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
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "thor"
4
+
5
+ module Equilibrium
6
+ module RepositoryUrlValidator
7
+ def self.validate(repository_url)
8
+ unless repository_url.include?("/")
9
+ raise Thor::Error, "Repository URL must be full format (e.g., 'gcr.io/project-id/image-name'), not '#{repository_url}'"
10
+ end
11
+ repository_url
12
+ end
13
+ end
14
+ end
@@ -14,8 +14,7 @@ module Equilibrium
14
14
  # @param error_prefix [String] Prefix for error messages (optional)
15
15
  # @raise [ValidationError] If validation fails
16
16
  def self.validate!(data, schema, error_prefix: "Schema validation failed")
17
- schemer = JSONSchemer.schema(schema)
18
- errors = schemer.validate(data).to_a
17
+ errors = validate(data, schema)
19
18
 
20
19
  return if errors.empty?
21
20
 
@@ -25,5 +24,14 @@ module Equilibrium
25
24
 
26
25
  raise ValidationError, "#{error_prefix}:\n#{error_messages.join("\n")}"
27
26
  end
27
+
28
+ # Validates data against a JSON Schema and returns validation errors
29
+ # @param data [Hash] The data to validate
30
+ # @param schema [Hash] The JSON Schema to validate against
31
+ # @return [Array] Array of validation errors (empty if valid)
32
+ def self.validate(data, schema)
33
+ schemer = JSONSchemer.schema(schema)
34
+ schemer.validate(data).to_a
35
+ end
28
36
  end
29
37
  end
@@ -8,25 +8,32 @@ module Equilibrium
8
8
  # Example output (perfect equilibrium):
9
9
  # {
10
10
  # "repository_url": "gcr.io/datadoghq/apm-inject",
11
+ # "repository_name": "apm-inject",
11
12
  # "expected_count": 28,
12
13
  # "actual_count": 28,
13
14
  # "missing_tags": {},
14
15
  # "unexpected_tags": {},
15
16
  # "mismatched_tags": {},
16
- # "status": "perfect",
17
- # "remediation_plan": []
17
+ # "status": "perfect"
18
18
  # }
19
19
  #
20
- # Example output (requires remediation):
20
+ # Example output (with discrepancies):
21
21
  # {
22
22
  # "repository_url": "gcr.io/datadoghq/example",
23
+ # "repository_name": "example",
23
24
  # "expected_count": 3,
24
25
  # "actual_count": 2,
25
26
  # "missing_tags": {
26
- # "latest": "sha256:abc123ef456789..."
27
+ # "latest": {
28
+ # "expected": "sha256:abc123ef456789...",
29
+ # "actual": ""
30
+ # }
27
31
  # },
28
32
  # "unexpected_tags": {
29
- # "dev": "sha256:xyz789ab123456..."
33
+ # "dev": {
34
+ # "expected": "",
35
+ # "actual": "sha256:xyz789ab123456..."
36
+ # }
30
37
  # },
31
38
  # "mismatched_tags": {
32
39
  # "0.1": {
@@ -34,37 +41,47 @@ module Equilibrium
34
41
  # "actual": "sha256:old123ef456789..."
35
42
  # }
36
43
  # },
37
- # "status": "missing_tags",
38
- # "remediation_plan": [
39
- # {
40
- # "action": "create_tag",
41
- # "tag": "latest",
42
- # "digest": "sha256:abc123ef456789...",
43
- # "command": "gcloud container images add-tag gcr.io/datadoghq/example@sha256:abc123... gcr.io/datadoghq/example:latest"
44
- # }
45
- # ]
44
+ # "status": "missing_tags"
46
45
  # }
47
46
  ANALYZER_OUTPUT = {
48
47
  "$schema" => "https://json-schema.org/draft/2020-12/schema",
49
48
  "title" => "Equilibrium Analyzer Output Schema",
50
49
  "description" => "Schema for output from 'equilibrium analyze --format=json' command",
51
50
  "type" => "object",
52
- "required" => ["repository_url", "expected_count", "actual_count", "missing_tags", "unexpected_tags", "mismatched_tags", "status", "remediation_plan"],
51
+ "required" => [
52
+ "repository_url",
53
+ "repository_name", "expected_count", "actual_count", "missing_tags", "unexpected_tags", "mismatched_tags", "status"
54
+ ],
53
55
  "properties" => {
54
56
  "repository_url" => {"type" => "string"},
57
+ "repository_name" => {"type" => "string"},
55
58
  "expected_count" => {"type" => "integer", "minimum" => 0},
56
59
  "actual_count" => {"type" => "integer", "minimum" => 0},
57
60
  "status" => {"enum" => ["perfect", "missing_tags", "mismatched", "extra_tags"]},
58
61
  "missing_tags" => {
59
62
  "type" => "object",
60
63
  "patternProperties" => {
61
- ".*" => {"type" => "string", "pattern" => "^sha256:[a-f0-9]{64}$"}
64
+ ".*" => {
65
+ "type" => "object",
66
+ "required" => ["expected", "actual"],
67
+ "properties" => {
68
+ "expected" => {"type" => "string", "pattern" => "^sha256:[a-f0-9]{64}$"},
69
+ "actual" => {"type" => "string", "enum" => [""]}
70
+ }
71
+ }
62
72
  }
63
73
  },
64
74
  "unexpected_tags" => {
65
75
  "type" => "object",
66
76
  "patternProperties" => {
67
- ".*" => {"type" => "string", "pattern" => "^sha256:[a-f0-9]{64}$"}
77
+ ".*" => {
78
+ "type" => "object",
79
+ "required" => ["expected", "actual"],
80
+ "properties" => {
81
+ "expected" => {"type" => "string", "enum" => [""]},
82
+ "actual" => {"type" => "string", "pattern" => "^sha256:[a-f0-9]{64}$"}
83
+ }
84
+ }
68
85
  }
69
86
  },
70
87
  "mismatched_tags" => {
@@ -79,21 +96,6 @@ module Equilibrium
79
96
  }
80
97
  }
81
98
  }
82
- },
83
- "remediation_plan" => {
84
- "type" => "array",
85
- "items" => {
86
- "type" => "object",
87
- "required" => ["action", "tag", "command"],
88
- "properties" => {
89
- "action" => {"enum" => ["create_tag", "update_tag", "remove_tag"]},
90
- "tag" => {"type" => "string"},
91
- "digest" => {"type" => "string", "pattern" => "^sha256:[a-f0-9]{64}$"},
92
- "old_digest" => {"type" => "string", "pattern" => "^sha256:[a-f0-9]{64}$"},
93
- "new_digest" => {"type" => "string", "pattern" => "^sha256:[a-f0-9]{64}$"},
94
- "command" => {"type" => "string"}
95
- }
96
- }
97
99
  }
98
100
  }
99
101
  }.freeze
@@ -3,25 +3,24 @@
3
3
  module Equilibrium
4
4
  module Schemas
5
5
  # JSON Schema for catalog output format
6
- # Used by `equilibrium catalog` command
6
+ # Used by `catalog` command
7
7
  #
8
8
  # Example output:
9
9
  # {
10
+ # "repository_url": "gcr.io/datadoghq/apm-inject",
11
+ # "repository_name": "apm-inject",
10
12
  # "images": [
11
13
  # {
12
- # "name": "apm-inject",
13
14
  # "tag": "latest",
14
15
  # "digest": "sha256:5fcfe7ac14f6eeb0fe086ac7021d013d764af573b8c2d98113abf26b4d09b58c",
15
16
  # "canonical_version": "0.43.2"
16
17
  # },
17
18
  # {
18
- # "name": "apm-inject",
19
19
  # "tag": "0",
20
20
  # "digest": "sha256:5fcfe7ac14f6eeb0fe086ac7021d013d764af573b8c2d98113abf26b4d09b58c",
21
21
  # "canonical_version": "0.43.2"
22
22
  # },
23
23
  # {
24
- # "name": "apm-inject",
25
24
  # "tag": "0.43",
26
25
  # "digest": "sha256:5fcfe7ac14f6eeb0fe086ac7021d013d764af573b8c2d98113abf26b4d09b58c",
27
26
  # "canonical_version": "0.43.1"
@@ -33,22 +32,30 @@ module Equilibrium
33
32
  "title" => "Tag Resolver Schema",
34
33
  "description" => "Schema for Resolve tag",
35
34
  "type" => "object",
35
+ "required" => ["repository_url", "repository_name", "images"],
36
36
  "properties" => {
37
+ "repository_url" => {
38
+ "type" => "string",
39
+ "description" => "Full repository URL (e.g., 'gcr.io/project-id/image-name')",
40
+ "minLength" => 1,
41
+ "pattern" => "^[a-zA-Z0-9.-]+(/[a-zA-Z0-9._-]+)*$"
42
+ },
43
+ "repository_name" => {
44
+ "type" => "string",
45
+ "description" => "Repository name extracted from URL (e.g., 'apm-inject')",
46
+ "minLength" => 1,
47
+ "pattern" => "^[a-zA-Z0-9._-]+$"
48
+ },
37
49
  "images" => {
38
50
  "type" => "array",
39
51
  "items" => {
40
52
  "type" => "object",
41
53
  "required" => [
42
- "name",
43
54
  "tag",
44
55
  "digest",
45
56
  "canonical_version"
46
57
  ],
47
58
  "properties" => {
48
- "name" => {
49
- "type" => "string",
50
- "description" => "The name of the image"
51
- },
52
59
  "tag" => {
53
60
  "type" => "string",
54
61
  "description" => "The mutable tag of the image"
@@ -3,7 +3,7 @@
3
3
  module Equilibrium
4
4
  module Schemas
5
5
  # JSON Schema for expected/actual command output format
6
- # Used by `equilibrium expected` and `equilibrium actual` commands
6
+ # Used by `expected`, `actual` and `uncatalog` commands
7
7
  #
8
8
  # Example output:
9
9
  # {
@@ -24,7 +24,7 @@ module Equilibrium
24
24
  "title" => "Docker Registry v2 Tags List Response Schema",
25
25
  "description" => "Schema for response from Docker Registry v2 API /v2/{name}/tags/list endpoint",
26
26
  "type" => "object",
27
- "required" => ["name", "tags"],
27
+ "required" => ["name", "tags", "manifest"],
28
28
  "properties" => {
29
29
  "name" => {
30
30
  "type" => "string",
@@ -43,9 +43,9 @@ module Equilibrium
43
43
  has_issues = true
44
44
  say "Missing Tags (should be created):"
45
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]
46
+ analysis[:missing_tags].each do |tag, details|
47
+ full_digest = details[:expected] || "unknown"
48
+ missing_table << [tag, full_digest]
49
49
  end
50
50
  print_table(missing_table, borders: true)
51
51
  say ""
@@ -58,10 +58,10 @@ module Equilibrium
58
58
  mismatched_table = [["Tag", "Expected", "Actual"]]
59
59
  analysis[:mismatched_tags].each do |tag, details|
60
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"
61
+ expected = details[:expected] || "unknown"
62
+ actual = details[:actual] || "unknown"
63
63
  else
64
- expected = details ? details.split(":").last[0..11] : "unknown"
64
+ expected = details || "unknown"
65
65
  actual = "unknown"
66
66
  end
67
67
  mismatched_table << [tag, expected, actual]
@@ -75,17 +75,17 @@ module Equilibrium
75
75
  has_issues = true
76
76
  say "Unexpected Tags (should be removed):"
77
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]
78
+ analysis[:unexpected_tags].each do |tag, details|
79
+ full_digest = details[:actual] || "unknown"
80
+ unexpected_table << [tag, full_digest]
81
81
  end
82
82
  print_table(unexpected_table, borders: true)
83
83
  say ""
84
84
  end
85
85
 
86
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'"
87
+ say "To see detailed analysis data, use:"
88
+ say " equilibrium analyze --expected expected.json --actual actual.json --format=json"
89
89
  else
90
90
  say "✓ Registry is in perfect equilibrium!", :green
91
91
  end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "tag_sorter"
4
+
5
+ module Equilibrium
6
+ class TagDataBuilder
7
+ def self.build_output(repository_url, repository_name, digests, canonical_versions)
8
+ {
9
+ "repository_url" => repository_url,
10
+ "repository_name" => repository_name,
11
+ "digests" => TagSorter.sort_descending(digests),
12
+ "canonical_versions" => TagSorter.sort_descending(canonical_versions)
13
+ }
14
+ end
15
+ end
16
+ end
@@ -8,6 +8,14 @@ module Equilibrium
8
8
  new.compute_virtual_tags(semantic_tags)
9
9
  end
10
10
 
11
+ def self.filter_semantic_tags(tagged_digests)
12
+ new.filter_semantic_tags(tagged_digests)
13
+ end
14
+
15
+ def self.filter_mutable_tags(tagged_digests)
16
+ new.filter_mutable_tags(tagged_digests)
17
+ end
18
+
11
19
  def compute_virtual_tags(semantic_tags)
12
20
  return {"digests" => {}, "canonical_versions" => {}} if semantic_tags.empty?
13
21
 
@@ -55,26 +63,32 @@ module Equilibrium
55
63
  {"digests" => digests, "canonical_versions" => canonical_versions}
56
64
  end
57
65
 
58
- def filter_semantic_tags(all_tags)
59
- # Filter semantic tags (canonical_tags.json): exact major.minor.patch format
60
- all_tags.select { |tag, _| semantic_version?(tag) }
66
+ def filter_semantic_tags(tagged_digests)
67
+ tagged_digests.select { |tag, _| semantic_version?(tag) }
61
68
  end
62
69
 
63
- def filter_mutable_tags(all_tags)
64
- # Filter mutable tags (actual_tags.json): latest, digits, or major.minor format
65
- all_tags.select { |tag, _| mutable_tag?(tag) }
70
+ def filter_mutable_tags(tagged_digests)
71
+ tagged_digests.select { |tag, _| mutable_tag?(tag) }
66
72
  end
67
73
 
68
74
  private
69
75
 
70
76
  def semantic_version?(tag)
71
- SemanticVersion.valid?(tag)
77
+ # Strictly validate MAJOR.MINOR.PATCH format where:
78
+ # - MAJOR, MINOR, PATCH are non-negative integers
79
+ # - No leading zeros (except for '0' itself)
80
+ # - No prefixes (like 'v1.2.3', 'release-1.2.3')
81
+ # - No suffixes (like '1.2.3-alpha', '1.2.3+build')
82
+ # - No prereleases (like '1.2.3-rc.1', '1.2.3-beta.2')
83
+ tag.match?(/^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)$/)
72
84
  end
73
85
 
74
86
  def mutable_tag?(tag)
75
- tag.match?(/^latest$/) ||
76
- tag.match?(/^[0-9]+$/) ||
77
- tag.match?(/^[0-9]+\.[0-9]+$/)
87
+ # Validate mutable tags: latest, major versions (digits only), or major.minor format
88
+ # - 'latest' is the special case for the highest overall version
89
+ # - Major versions: non-negative integers without leading zeros (e.g., '1', '0', '42')
90
+ # - Minor versions: MAJOR.MINOR format with same zero-leading rules (e.g., '1.2', '0.9')
91
+ tag.match?(/^(latest|(0|[1-9]\d*)(\.(0|[1-9]\d*))?)$/)
78
92
  end
79
93
  end
80
94
  end
@@ -4,10 +4,6 @@ module Equilibrium
4
4
  class TagSorter
5
5
  # Sort tags in descending version order: latest first, then major versions (descending), then minor versions (descending)
6
6
  def self.sort_descending(tags_hash)
7
- new.sort_descending(tags_hash)
8
- end
9
-
10
- def sort_descending(tags_hash)
11
7
  return {} if tags_hash.nil? || tags_hash.empty?
12
8
 
13
9
  sorted = {}
@@ -20,10 +16,10 @@ module Equilibrium
20
16
  # Sort other tags by version (descending)
21
17
  other_tags = tags_hash.keys.reject { |k| k == "latest" }
22
18
  sorted_tags = other_tags.sort_by do |tag|
23
- if major_version?(tag)
19
+ if tag.match?(/^[0-9]+$/)
24
20
  # Major version: sort by numeric value (descending)
25
21
  [-tag.to_i]
26
- elsif minor_version?(tag)
22
+ elsif tag.match?(/^[0-9]+\.[0-9]+$/)
27
23
  # Minor version: sort by version (descending)
28
24
  parts = tag.split(".").map(&:to_i)
29
25
  [-parts[0], -parts[1]]
@@ -39,15 +35,5 @@ module Equilibrium
39
35
 
40
36
  sorted
41
37
  end
42
-
43
- private
44
-
45
- def major_version?(tag)
46
- tag.match?(/^[0-9]+$/)
47
- end
48
-
49
- def minor_version?(tag)
50
- tag.match?(/^[0-9]+\.[0-9]+$/)
51
- end
52
38
  end
53
39
  end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "repository_tags_service"
4
+ require_relative "tag_data_builder"
5
+
6
+ module Equilibrium
7
+ class TagsOperationService
8
+ def self.generate_expected_output(repository_url)
9
+ tag_data = RepositoryTagsService.generate_expected_tags(repository_url)
10
+ repository_name = repository_url.split("/").last
11
+
12
+ TagDataBuilder.build_output(
13
+ repository_url,
14
+ repository_name,
15
+ tag_data["digests"],
16
+ tag_data["canonical_versions"]
17
+ )
18
+ end
19
+
20
+ def self.generate_actual_output(repository_url)
21
+ tag_data = RepositoryTagsService.generate_actual_tags(repository_url)
22
+ repository_name = repository_url.split("/").last
23
+
24
+ TagDataBuilder.build_output(
25
+ repository_url,
26
+ repository_name,
27
+ tag_data["digests"],
28
+ tag_data["canonical_versions"]
29
+ )
30
+ end
31
+ end
32
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Equilibrium
4
- VERSION = "0.1.1"
4
+ VERSION = "0.3.0"
5
5
  end
data/lib/equilibrium.rb CHANGED
@@ -1,7 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative "equilibrium/version"
4
- require_relative "equilibrium/semantic_version"
5
4
  require_relative "equilibrium/registry_client"
6
5
  require_relative "equilibrium/tag_processor"
7
6
  require_relative "equilibrium/tag_sorter"
metadata CHANGED
@@ -1,14 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: equilibrium
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Tony Hsu
8
- autorequire:
9
8
  bindir: exe
10
9
  cert_chain: []
11
- date: 2025-08-06 00:00:00.000000000 Z
10
+ date: 1980-01-02 00:00:00.000000000 Z
12
11
  dependencies:
13
12
  - !ruby/object:Gem::Dependency
14
13
  name: thor
@@ -71,29 +70,39 @@ files:
71
70
  - exe/equilibrium
72
71
  - lib/equilibrium.rb
73
72
  - lib/equilibrium/analyzer.rb
73
+ - lib/equilibrium/canonical_version_mapper.rb
74
74
  - lib/equilibrium/catalog_builder.rb
75
75
  - lib/equilibrium/cli.rb
76
+ - lib/equilibrium/commands/actual_command.rb
77
+ - lib/equilibrium/commands/analyze_command.rb
78
+ - lib/equilibrium/commands/catalog_command.rb
79
+ - lib/equilibrium/commands/expected_command.rb
80
+ - lib/equilibrium/commands/uncatalog_command.rb
81
+ - lib/equilibrium/commands/version_command.rb
82
+ - lib/equilibrium/mixins/error_handling.rb
83
+ - lib/equilibrium/mixins/input_output.rb
76
84
  - lib/equilibrium/registry_client.rb
85
+ - lib/equilibrium/repository_tags_service.rb
86
+ - lib/equilibrium/repository_url_validator.rb
77
87
  - lib/equilibrium/schema_validator.rb
78
88
  - lib/equilibrium/schemas/analyzer_output.rb
79
89
  - lib/equilibrium/schemas/catalog.rb
80
90
  - lib/equilibrium/schemas/expected_actual.rb
81
91
  - lib/equilibrium/schemas/registry_api.rb
82
- - lib/equilibrium/semantic_version.rb
83
92
  - lib/equilibrium/summary_formatter.rb
93
+ - lib/equilibrium/tag_data_builder.rb
84
94
  - lib/equilibrium/tag_processor.rb
85
95
  - lib/equilibrium/tag_sorter.rb
96
+ - lib/equilibrium/tags_operation_service.rb
86
97
  - lib/equilibrium/version.rb
87
- - tmp/.gitkeep
88
98
  homepage: https://github.com/TonyCTHsu/equilibrium
89
99
  licenses:
90
100
  - MIT
91
101
  metadata:
92
102
  homepage_uri: https://github.com/TonyCTHsu/equilibrium
93
103
  source_code_uri: https://github.com/TonyCTHsu/equilibrium
94
- changelog_uri: https://github.com/TonyCTHsu/equilibrium/blob/master/CHANGELOG.md
95
- github_repo: ssh://github.com/TonyCTHsu/tobee
96
- post_install_message:
104
+ changelog_uri: https://github.com/TonyCTHsu/equilibrium/blob/v0.3.0/CHANGELOG.md
105
+ github_repo: ssh://github.com/TonyCTHsu/equilibrium
97
106
  rdoc_options: []
98
107
  require_paths:
99
108
  - lib
@@ -108,8 +117,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
108
117
  - !ruby/object:Gem::Version
109
118
  version: '0'
110
119
  requirements: []
111
- rubygems_version: 3.4.19
112
- signing_key:
120
+ rubygems_version: 3.6.9
113
121
  specification_version: 4
114
122
  summary: Container image tag validation tool
115
123
  test_files: []
@@ -1,24 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Equilibrium
4
- module SemanticVersion
5
- # Strictly validate MAJOR.MINOR.PATCH format where:
6
- # - MAJOR, MINOR, PATCH are non-negative integers
7
- # - No leading zeros (except for '0' itself)
8
- # - No prefixes (like 'v1.2.3', 'release-1.2.3')
9
- # - No suffixes (like '1.2.3-alpha', '1.2.3+build')
10
- # - No prereleases (like '1.2.3-rc.1', '1.2.3-beta.2')
11
- def self.valid?(tag)
12
- # Strict regex: each component must be either '0' or a number without leading zeros
13
- return false unless tag.match?(/^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)$/)
14
-
15
- # Additional validation: ensure it's a valid Gem::Version
16
- begin
17
- Gem::Version.new(tag)
18
- true
19
- rescue ArgumentError
20
- false
21
- end
22
- end
23
- end
24
- end
data/tmp/.gitkeep DELETED
@@ -1,2 +0,0 @@
1
- # This file ensures the tmp/ directory is tracked by git
2
- # while keeping all other contents ignored via .gitignore