gemstash 1.0.0.pre.1-java → 2.5.0-java

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 (99) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +295 -0
  3. data/exe/gemstash +3 -0
  4. data/lib/gemstash/api_key_authorization.rb +32 -0
  5. data/lib/gemstash/authorization.rb +15 -8
  6. data/lib/gemstash/cache.rb +42 -2
  7. data/lib/gemstash/cli/authorize.rb +52 -9
  8. data/lib/gemstash/cli/base.rb +14 -6
  9. data/lib/gemstash/cli/setup.rb +67 -39
  10. data/lib/gemstash/cli/start.rb +6 -2
  11. data/lib/gemstash/cli/status.rb +3 -1
  12. data/lib/gemstash/cli/stop.rb +4 -1
  13. data/lib/gemstash/cli.rb +59 -1
  14. data/lib/gemstash/config.ru +4 -3
  15. data/lib/gemstash/configuration.rb +56 -8
  16. data/lib/gemstash/db/authorization.rb +5 -3
  17. data/lib/gemstash/db/cached_rubygem.rb +20 -0
  18. data/lib/gemstash/db/dependency.rb +2 -0
  19. data/lib/gemstash/db/rubygem.rb +3 -0
  20. data/lib/gemstash/db/upstream.rb +15 -0
  21. data/lib/gemstash/db/version.rb +25 -2
  22. data/lib/gemstash/db.rb +5 -0
  23. data/lib/gemstash/dependencies.rb +6 -2
  24. data/lib/gemstash/env.rb +44 -13
  25. data/lib/gemstash/gem_fetcher.rb +5 -3
  26. data/lib/gemstash/gem_pusher.rb +25 -18
  27. data/lib/gemstash/gem_source/dependency_caching.rb +4 -4
  28. data/lib/gemstash/gem_source/private_source.rb +34 -50
  29. data/lib/gemstash/gem_source/rack_middleware.rb +3 -0
  30. data/lib/gemstash/gem_source/upstream_source.rb +71 -27
  31. data/lib/gemstash/gem_source.rb +4 -2
  32. data/lib/gemstash/gem_yanker.rb +14 -4
  33. data/lib/gemstash/health.rb +55 -0
  34. data/lib/gemstash/http_client.rb +15 -5
  35. data/lib/gemstash/logging.rb +19 -7
  36. data/lib/gemstash/man/gemstash-authorize.1 +54 -0
  37. data/lib/gemstash/man/gemstash-authorize.1.txt +52 -0
  38. data/lib/gemstash/man/gemstash-configuration.5 +186 -0
  39. data/lib/gemstash/man/gemstash-configuration.5.txt +208 -0
  40. data/lib/gemstash/man/gemstash-customize.7 +273 -0
  41. data/lib/gemstash/man/gemstash-customize.7.txt +184 -0
  42. data/lib/gemstash/man/gemstash-debugging.7 +30 -0
  43. data/lib/gemstash/man/gemstash-debugging.7.txt +27 -0
  44. data/lib/gemstash/man/gemstash-deploy.7 +63 -0
  45. data/lib/gemstash/man/gemstash-deploy.7.txt +57 -0
  46. data/lib/gemstash/man/gemstash-mirror.7 +34 -0
  47. data/lib/gemstash/man/gemstash-mirror.7.txt +31 -0
  48. data/lib/gemstash/man/gemstash-multiple-sources.7 +131 -0
  49. data/lib/gemstash/man/gemstash-multiple-sources.7.txt +116 -0
  50. data/lib/gemstash/man/gemstash-private-gems.7 +191 -0
  51. data/lib/gemstash/man/gemstash-private-gems.7.txt +154 -0
  52. data/lib/gemstash/man/gemstash-readme.7 +199 -0
  53. data/lib/gemstash/man/gemstash-readme.7.txt +177 -0
  54. data/lib/gemstash/man/gemstash-setup.1 +38 -0
  55. data/lib/gemstash/man/gemstash-setup.1.txt +38 -0
  56. data/lib/gemstash/man/gemstash-start.1 +23 -0
  57. data/lib/gemstash/man/gemstash-start.1.txt +24 -0
  58. data/lib/gemstash/man/gemstash-status.1 +17 -0
  59. data/lib/gemstash/man/gemstash-status.1.txt +20 -0
  60. data/lib/gemstash/man/gemstash-stop.1 +17 -0
  61. data/lib/gemstash/man/gemstash-stop.1.txt +20 -0
  62. data/lib/gemstash/man/gemstash-version.1 +17 -0
  63. data/lib/gemstash/man/gemstash-version.1.txt +19 -0
  64. data/lib/gemstash/migrations/01_gem_dependencies.rb +11 -9
  65. data/lib/gemstash/migrations/02_authorizations.rb +4 -2
  66. data/lib/gemstash/migrations/03_cached_gems.rb +26 -0
  67. data/lib/gemstash/migrations/04_health_tests.rb +10 -0
  68. data/lib/gemstash/migrations/05_authorization_names.rb +10 -0
  69. data/lib/gemstash/puma.rb +5 -3
  70. data/lib/gemstash/rack_env_rewriter.rb +11 -2
  71. data/lib/gemstash/specs_builder.rb +25 -15
  72. data/lib/gemstash/storage.rb +175 -32
  73. data/lib/gemstash/upstream.rb +43 -8
  74. data/lib/gemstash/version.rb +4 -2
  75. data/lib/gemstash/web.rb +13 -8
  76. data/lib/gemstash.rb +6 -2
  77. metadata +135 -110
  78. data/.gitignore +0 -10
  79. data/.rspec +0 -2
  80. data/.rubocop-bundler.yml +0 -92
  81. data/.rubocop-relax.yml +0 -11
  82. data/.rubocop.yml +0 -8
  83. data/.travis.yml +0 -20
  84. data/Gemfile +0 -4
  85. data/README.md +0 -139
  86. data/Rakefile +0 -35
  87. data/bin/console +0 -14
  88. data/bin/gemstash +0 -3
  89. data/bin/setup +0 -5
  90. data/docs/config.md +0 -136
  91. data/docs/debug.md +0 -24
  92. data/docs/deploy.md +0 -30
  93. data/docs/mirror.md +0 -30
  94. data/docs/multiple_sources.md +0 -68
  95. data/docs/private_gems.md +0 -140
  96. data/docs/reference.md +0 -308
  97. data/gemstash.gemspec +0 -47
  98. data/gemstash.png +0 -0
  99. data/lib/gemstash/gem_unyanker.rb +0 -61
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ Sequel.migration do
4
+ change do
5
+ create_table :upstreams do
6
+ primary_key :id
7
+ String :uri, size: 191, null: false
8
+ String :host_id, size: 191, null: false
9
+ DateTime :created_at, null: false
10
+ DateTime :updated_at, null: false
11
+ index [:uri], unique: true
12
+ index [:host_id], unique: true
13
+ end
14
+
15
+ create_table :cached_rubygems do
16
+ primary_key :id
17
+ Integer :upstream_id, null: false
18
+ String :name, size: 191, null: false
19
+ String :resource_type, size: 191, null: false
20
+ DateTime :created_at, null: false
21
+ DateTime :updated_at, null: false
22
+ index %i[upstream_id resource_type name], unique: true
23
+ index [:name]
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ Sequel.migration do
4
+ change do
5
+ create_table :health_tests do
6
+ primary_key :id
7
+ String :string
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ Sequel.migration do
4
+ change do
5
+ alter_table :authorizations do
6
+ add_column :name, String, :size => 191
7
+ add_index [:name], :unique => true
8
+ end
9
+ end
10
+ end
data/lib/gemstash/puma.rb CHANGED
@@ -1,6 +1,8 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "gemstash"
2
4
 
3
- threads 0, 16
4
- bind "#{Gemstash::Env.current.config[:bind]}"
5
- workers 1
5
+ threads 0, Gemstash::Env.current.config[:puma_threads].to_i
6
+ bind Gemstash::Env.current.config[:bind].to_s
7
+ workers Gemstash::Env.current.config[:puma_workers].to_i unless RUBY_PLATFORM == "java"
6
8
  rackup Gemstash::Env.current.rackup
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "gemstash"
2
4
  require "forwardable"
3
5
 
@@ -33,8 +35,15 @@ module Gemstash
33
35
  def rewrite
34
36
  check_match
35
37
  log_start = "Rewriting '#{@rack_env["REQUEST_URI"]}'"
36
- @rack_env["REQUEST_URI"][@request_uri_match.begin(0)...@request_uri_match.end(0)] = ""
37
- @rack_env["PATH_INFO"][@path_info_match.begin(0)...@path_info_match.end(0)] = ""
38
+
39
+ new_request_uri = @rack_env["REQUEST_URI"].dup
40
+ new_request_uri[@request_uri_match.begin(0)...@request_uri_match.end(0)] = ""
41
+
42
+ new_path_info = @rack_env["PATH_INFO"].dup
43
+ new_path_info[@path_info_match.begin(0)...@path_info_match.end(0)] = ""
44
+
45
+ @rack_env["REQUEST_URI"] = new_request_uri
46
+ @rack_env["PATH_INFO"] = new_path_info
38
47
  log.info "#{log_start} to '#{@rack_env["REQUEST_URI"]}'"
39
48
  end
40
49
 
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "gemstash"
2
4
  require "stringio"
3
5
  require "zlib"
@@ -6,33 +8,34 @@ module Gemstash
6
8
  # Builds a Marshal'ed and GZipped array of arrays containing specs as:
7
9
  # [name, Gem::Version, platform]
8
10
  class SpecsBuilder
11
+ include Gemstash::Env::Helper
9
12
  attr_reader :result
10
13
 
11
- # Used for the /private/specs.4.8.gz endpoint. Fetches non-prerelease,
12
- # indexed private gems.
13
- def self.all
14
- new.build
15
- end
16
-
17
- # Used for the /private/prerelease_specs.4.8.gz endpoint. Fetches
18
- # prerelease, indexed private gems.
19
- def self.prerelease
20
- new(prerelease: true).build
14
+ def self.serve(app)
15
+ prerelease = app.params.fetch(:prerelease, false)
16
+ latest = app.params.fetch(:latest, false)
17
+ app.content_type "application/octet-stream"
18
+ new(app.auth, prerelease: prerelease, latest: latest).serve
21
19
  end
22
20
 
23
21
  def self.invalidate_stored
24
22
  storage = Gemstash::Storage.for("private").for("specs_collection")
25
23
  storage.resource("specs.4.8.gz").delete(:specs)
24
+ storage.resource("latest_specs.4.8.gz").delete(:specs)
26
25
  storage.resource("prerelease_specs.4.8.gz").delete(:specs)
27
26
  end
28
27
 
29
- def initialize(prerelease: false)
28
+ def initialize(auth, prerelease: false, latest: false)
29
+ @auth = auth
30
30
  @prerelease = prerelease
31
+ @latest = latest
31
32
  end
32
33
 
33
- def build
34
+ def serve
35
+ check_auth if gemstash_env.config[:protected_fetch]
34
36
  fetch_from_storage
35
37
  return result if result
38
+
36
39
  fetch_versions
37
40
  marshal
38
41
  gzip
@@ -47,7 +50,9 @@ module Gemstash
47
50
  end
48
51
 
49
52
  def fetch_resource
50
- if @prerelease
53
+ if @latest
54
+ storage.resource("latest_specs.4.8.gz")
55
+ elsif @prerelease
51
56
  storage.resource("prerelease_specs.4.8.gz")
52
57
  else
53
58
  storage.resource("specs.4.8.gz")
@@ -57,14 +62,15 @@ module Gemstash
57
62
  def fetch_from_storage
58
63
  specs = fetch_resource
59
64
  return unless specs.exist?(:specs)
65
+
60
66
  @result = specs.load(:specs).content(:specs)
61
- rescue
67
+ rescue StandardError
62
68
  # On the off-chance of a race condition between specs.exist? and specs.load
63
69
  @result = nil
64
70
  end
65
71
 
66
72
  def fetch_versions
67
- @versions = Gemstash::DB::Version.for_spec_collection(prerelease: @prerelease).map(&:to_spec)
73
+ @versions = Gemstash::DB::Version.for_spec_collection(prerelease: @prerelease, latest: @latest).map(&:to_spec)
68
74
  end
69
75
 
70
76
  def marshal
@@ -89,5 +95,9 @@ module Gemstash
89
95
  def store_result
90
96
  fetch_resource.save(specs: @result)
91
97
  end
98
+
99
+ def check_auth
100
+ @auth.check("fetch")
101
+ end
92
102
  end
93
103
  end
@@ -1,68 +1,114 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "gemstash"
2
4
  require "digest"
3
- require "pathname"
4
5
  require "fileutils"
6
+ require "pathname"
5
7
  require "yaml"
6
8
 
7
9
  module Gemstash
8
- #:nodoc:
10
+ # The entry point into the storage engine for storing cached gems, specs, and
11
+ # private gems.
9
12
  class Storage
10
13
  extend Gemstash::Env::Helper
11
14
  VERSION = 1
12
15
 
13
- # If the storage engine detects something that was stored with a newer
14
- # version of the storage engine, this error will be thrown.
16
+ # If the storage engine detects the base cache directory was originally
17
+ # initialized with a newer version, this error is thrown.
15
18
  class VersionTooNew < StandardError
19
+ def initialize(folder, version)
20
+ super("Gemstash storage version #{Gemstash::Storage::VERSION} does " \
21
+ "not support version #{version} found at #{folder}")
22
+ end
16
23
  end
17
24
 
25
+ # This object should not be constructed directly, but instead via
26
+ # {for} and {#for}.
18
27
  def initialize(folder, root: true)
19
- check_engine if root
20
28
  @folder = folder
29
+ check_storage_version if root
21
30
  FileUtils.mkpath(@folder) unless Dir.exist?(@folder)
22
31
  end
23
32
 
33
+ # Fetch the resource with the given +id+ within this storage.
34
+ #
35
+ # @param id [String] the id of the resource to fetch
36
+ # @return [Gemstash::Resource] a new resource instance from the +id+
24
37
  def resource(id)
25
38
  Resource.new(@folder, id)
26
39
  end
27
40
 
41
+ # Fetch a nested entry from this instance in the storage engine.
42
+ #
43
+ # @param child [String] the name of the nested entry to load
44
+ # @return [Gemstash::Storage] a new storage instance for the +child+
28
45
  def for(child)
29
46
  Storage.new(File.join(@folder, child), root: false)
30
47
  end
31
48
 
49
+ # Fetch a base entry in the storage engine.
50
+ #
51
+ # @param name [String] the name of the entry to load
52
+ # @return [Gemstash::Storage] a new storage instance for the +name+
32
53
  def self.for(name)
33
54
  new(gemstash_env.base_file(name))
34
55
  end
35
56
 
57
+ # Read the global metadata for Gemstash and the storage engine. If the
58
+ # metadata hasn't been stored yet, it will be created.
59
+ #
60
+ # @return [Hash] the metadata about Gemstash and the storage engine
36
61
  def self.metadata
37
62
  file = gemstash_env.base_file("metadata.yml")
38
63
 
39
64
  unless File.exist?(file)
40
- File.write(file, { storage_version: Gemstash::Storage::VERSION,
41
- gemstash_version: Gemstash::VERSION }.to_yaml)
65
+ gemstash_env.atomic_write(file) do |f|
66
+ f.write({ storage_version: Gemstash::Storage::VERSION,
67
+ gemstash_version: Gemstash::VERSION }.to_yaml)
68
+ end
42
69
  end
43
70
 
44
- YAML.load_file(file)
71
+ YAML.safe_load_file(file, permitted_classes: [Symbol])
45
72
  end
46
73
 
47
74
  private
48
75
 
49
- def check_engine
76
+ def check_storage_version
50
77
  version = Gemstash::Storage.metadata[:storage_version]
51
78
  return if version <= Gemstash::Storage::VERSION
52
- raise Gemstash::Storage::VersionTooNew, "Storage engine is out of date: #{version}"
79
+
80
+ raise Gemstash::Storage::VersionTooNew.new(@folder, version)
53
81
  end
54
82
 
55
83
  def path_valid?(path)
56
84
  return false if path.nil?
57
85
  return false unless File.writable?(path)
86
+
58
87
  true
59
88
  end
60
89
  end
61
90
 
62
- #:nodoc:
91
+ # A resource within the storage engine. The resource may have 1 or more files
92
+ # associated with it along with a metadata Hash that is stored in a YAML file.
63
93
  class Resource
94
+ include Gemstash::Env::Helper
64
95
  include Gemstash::Logging
65
96
  attr_reader :name, :folder
97
+
98
+ VERSION = 1
99
+
100
+ # If the storage engine detects a resource was originally saved from a newer
101
+ # version, this error is thrown.
102
+ class VersionTooNew < StandardError
103
+ def initialize(name, folder, version)
104
+ super("Gemstash resource version #{Gemstash::Resource::VERSION} does " \
105
+ "not support version #{version} for resource #{name.inspect} " \
106
+ "found at #{folder}")
107
+ end
108
+ end
109
+
110
+ # This object should not be constructed directly, but instead via
111
+ # {Gemstash::Storage#resource}.
66
112
  def initialize(folder, name)
67
113
  @base_path = folder
68
114
  @name = name
@@ -76,8 +122,16 @@ module Gemstash
76
122
  digest = Digest::MD5.hexdigest(@name)
77
123
  child_folder = "#{safe_name}-#{digest}"
78
124
  @folder = File.join(@base_path, *trie_parents, child_folder)
125
+ @properties = nil
79
126
  end
80
127
 
128
+ # When +key+ is nil, this will test if this resource exists with any
129
+ # content. If a +key+ is provided, this will test that the resource exists
130
+ # with at least the given +key+ file. The +key+ corresponds to the +content+
131
+ # key provided to {#save}.
132
+ #
133
+ # @param key [Symbol, nil] the key of the content to check existence
134
+ # @return [Boolean] true if the indicated content exists
81
135
  def exist?(key = nil)
82
136
  if key
83
137
  File.exist?(properties_filename) && File.exist?(content_filename(key))
@@ -86,6 +140,25 @@ module Gemstash
86
140
  end
87
141
  end
88
142
 
143
+ # Save one or more files for this resource given by the +content+ hash.
144
+ # Metadata properties about the file(s) may be provided in the optional
145
+ # +properties+ parameter. The keys in the content hash correspond to the
146
+ # file name for this resource, while the values will be the content stored
147
+ # for that key.
148
+ #
149
+ # Separate calls to save for the same resource will replace existing files,
150
+ # and add new ones. Properties on additional calls will be merged with
151
+ # existing properties. Nested hashes in properties will also be merged.
152
+ #
153
+ # Examples:
154
+ #
155
+ # Gemstash::Storage.for("foo").resource("bar").save(baz: "qux")
156
+ # Gemstash::Storage.for("foo").resource("bar").save(baz: "one", qux: "two")
157
+ # Gemstash::Storage.for("foo").resource("bar").save({ baz: "qux" }, meta: true)
158
+ #
159
+ # @param content [Hash{Symbol => String}] files to save, *must not be nil*
160
+ # @param properties [Hash, nil] metadata properties related to this resource
161
+ # @return [Gemstash::Resource] self for chaining purposes
89
162
  def save(content, properties = nil)
90
163
  content.each do |key, value|
91
164
  save_content(key, value)
@@ -95,61 +168,129 @@ module Gemstash
95
168
  self
96
169
  end
97
170
 
171
+ # Fetch the content for the given +key+. This will load and cache the
172
+ # properties and the content of the +key+. The +key+ corresponds to the
173
+ # +content+ key provided to {#save}.
174
+ #
175
+ # @param key [Symbol] the key of the content to load
176
+ # @return [String] the content stored in the +key+
98
177
  def content(key)
178
+ @content ||= {}
179
+ load(key) unless @content.include?(key)
99
180
  @content[key]
100
181
  end
101
182
 
183
+ # Fetch the metadata properties for this resource. The properties will be
184
+ # cached for future calls.
185
+ #
186
+ # @return [Hash] the metadata properties for this resource
102
187
  def properties
188
+ load_properties
103
189
  @properties || {}
104
190
  end
105
191
 
192
+ # Update the metadata properties of this resource. The +props+ will be
193
+ # merged with any existing properties. Nested hashes in the properties will
194
+ # also be merged.
195
+ #
196
+ # @param props [Hash] the properties to add
197
+ # @return [Gemstash::Resource] self for chaining purposes
106
198
  def update_properties(props)
107
- load_properties
199
+ load_properties(force: true)
200
+
201
+ deep_merge = proc do |_, old_value, new_value|
202
+ if old_value.is_a?(Hash) && new_value.is_a?(Hash)
203
+ old_value.merge(new_value, &deep_merge)
204
+ else
205
+ new_value
206
+ end
207
+ end
208
+
209
+ props = properties.merge(props || {}, &deep_merge)
108
210
  save_properties(properties.merge(props || {}))
109
211
  self
110
212
  end
111
213
 
112
- def load(key)
113
- raise "Resource #{@name} has no content to load" unless exist?(key)
114
- load_properties
115
- @content ||= {}
116
- @content[key] = read_file(content_filename(key))
117
- self
214
+ # Check if the metadata properties includes the +keys+. The +keys+ represent
215
+ # a nested path in the properties to check.
216
+ #
217
+ # Examples:
218
+ #
219
+ # resource = Gemstash::Storage.for("x").resource("y")
220
+ # resource.save({ file: "content" }, foo: "one", bar: { baz: "qux" })
221
+ # resource.has_property?(:foo) # true
222
+ # resource.has_property?(:bar, :baz) # true
223
+ # resource.has_property?(:missing) # false
224
+ # resource.has_property?(:foo, :bar) # false
225
+ #
226
+ # @param keys [Array<Object>] one or more keys pointing to a property
227
+ # @return [Boolean] whether the nested keys points to a valid property
228
+ def property?(*keys)
229
+ keys.inject(node: properties, result: true) do |memo, key|
230
+ if memo[:result]
231
+ memo[:result] = memo[:node].is_a?(Hash) && memo[:node].include?(key)
232
+ memo[:node] = memo[:node][key] if memo[:result]
233
+ end
234
+
235
+ memo
236
+ end[:result]
118
237
  end
119
238
 
239
+ # Delete the content for the given +key+. If the +key+ is the last one for
240
+ # this resource, the metadata properties will be deleted as well. The +key+
241
+ # corresponds to the +content+ key provided to {#save}.
242
+ #
243
+ # The resource will be reset afterwards, clearing any cached content or
244
+ # properties.
245
+ #
246
+ # Does nothing if the key doesn't {#exist?}.
247
+ #
248
+ # @param key [Symbol] the key of the content to delete
249
+ # @return [Gemstash::Resource] self for chaining purposes
120
250
  def delete(key)
121
251
  return self unless exist?(key)
122
252
 
123
253
  begin
124
254
  File.delete(content_filename(key))
125
- rescue => e
255
+ rescue StandardError => e
126
256
  log_error "Failed to delete stored content at #{content_filename(key)}", e, level: :warn
127
257
  end
128
258
 
129
259
  begin
130
260
  File.delete(properties_filename) unless content?
131
- rescue => e
261
+ rescue StandardError => e
132
262
  log_error "Failed to delete stored properties at #{properties_filename}", e, level: :warn
133
263
  end
134
264
 
135
- return self
265
+ self
136
266
  ensure
137
267
  reset
138
268
  end
139
269
 
140
270
  private
141
271
 
142
- def load_properties
272
+ def load(key)
273
+ raise "Resource #{@name} has no #{key.inspect} content to load" unless exist?(key)
274
+
275
+ load_properties # Ensures storage version is checked
276
+ @content ||= {}
277
+ @content[key] = read_file(content_filename(key))
278
+ end
279
+
280
+ def load_properties(force: false)
281
+ return if @properties && !force
143
282
  return unless File.exist?(properties_filename)
144
- @properties = YAML.load_file(properties_filename)
145
- check_version
283
+
284
+ @properties = YAML.safe_load_file(properties_filename, permitted_classes: [Symbol]) || {}
285
+ check_resource_version
146
286
  end
147
287
 
148
- def check_version
149
- version = @properties[:gemstash_storage_version]
150
- return if version <= Gemstash::Storage::VERSION
288
+ def check_resource_version
289
+ version = @properties[:gemstash_resource_version]
290
+ return if version <= Gemstash::Resource::VERSION
291
+
151
292
  reset
152
- raise Gemstash::Storage::VersionTooNew, "Resource was stored with a newer storage: #{version}"
293
+ raise Gemstash::Resource::VersionTooNew.new(name, folder, version)
153
294
  end
154
295
 
155
296
  def reset
@@ -159,8 +300,9 @@ module Gemstash
159
300
 
160
301
  def content?
161
302
  return false unless Dir.exist?(@folder)
162
- entries = Dir.entries(@folder).reject {|file| file =~ /\A\.\.?\z/ }
163
- !entries.empty? && entries != %w(properties.yaml)
303
+
304
+ entries = Dir.entries(@folder).reject {|file| file =~ /\A\.\.?\z/ || file == "properties.yaml" }
305
+ !entries.empty?
164
306
  end
165
307
 
166
308
  def sanitize(name)
@@ -175,7 +317,7 @@ module Gemstash
175
317
 
176
318
  def save_properties(props)
177
319
  props ||= {}
178
- props = { gemstash_storage_version: Gemstash::Storage::VERSION }.merge(props)
320
+ props = { gemstash_resource_version: Gemstash::Resource::VERSION }.merge(props)
179
321
  store(properties_filename, props.to_yaml)
180
322
  @properties = props
181
323
  end
@@ -187,7 +329,7 @@ module Gemstash
187
329
 
188
330
  def save_file(filename)
189
331
  content = yield
190
- File.open(filename, "wb") {|f| f.write(content) }
332
+ gemstash_env.atomic_write(filename) {|f| f.write(content) }
191
333
  end
192
334
 
193
335
  def read_file(filename)
@@ -197,6 +339,7 @@ module Gemstash
197
339
  def content_filename(key)
198
340
  name = sanitize(key.to_s)
199
341
  raise "Invalid content key #{key.inspect}" if name.empty?
342
+
200
343
  File.join(@folder, name)
201
344
  end
202
345
 
@@ -1,18 +1,24 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "digest"
4
+ require "forwardable"
5
+ require "uri"
2
6
 
3
7
  module Gemstash
4
- #:nodoc:
8
+ # :nodoc:
5
9
  class Upstream
6
10
  extend Forwardable
7
11
 
8
- attr_reader :user_agent
12
+ attr_reader :user_agent, :uri
9
13
 
10
- def_delegators :@uri, :scheme, :host, :user, :password, :to_s
14
+ def_delegators :@uri, :scheme, :host, :to_s
11
15
 
12
16
  def initialize(upstream, user_agent: nil)
13
- @uri = URI(URI.decode(upstream.to_s))
17
+ url = CGI.unescape(upstream.to_s)
18
+ url = "https://#{url}" unless %r{^https?://}.match?(url)
19
+ @uri = URI(url)
14
20
  @user_agent = user_agent
15
- raise "URL '#{@uri}' is not valid!" unless @uri.to_s =~ URI.regexp
21
+ raise "URL '#{@uri}' is not valid!" unless @uri.to_s&.match?(URI::DEFAULT_PARSER.make_regexp)
16
22
  end
17
23
 
18
24
  def url(path = nil, params = nil)
@@ -20,7 +26,7 @@ module Gemstash
20
26
 
21
27
  unless path.to_s.empty?
22
28
  base = "#{base}/" unless base.end_with?("/")
23
- path = path[1..-1] if path.to_s.start_with?("/")
29
+ path = path[1..] if path.to_s.start_with?("/")
24
30
  end
25
31
 
26
32
  params = "?#{params}" if !params.nil? && !params.empty?
@@ -28,7 +34,7 @@ module Gemstash
28
34
  end
29
35
 
30
36
  def auth?
31
- !user.to_s.empty? && !password.to_s.empty?
37
+ !user.to_s.empty? || !password.to_s.empty?
32
38
  end
33
39
 
34
40
  # Utilized as the parent directory for cached gems
@@ -36,13 +42,42 @@ module Gemstash
36
42
  @host_id ||= "#{host}_#{hash}"
37
43
  end
38
44
 
45
+ def user
46
+ env_auth_user || @uri.user
47
+ end
48
+
49
+ def password
50
+ env_auth_pass || @uri.password
51
+ end
52
+
39
53
  private
40
54
 
41
55
  def hash
42
56
  Digest::MD5.hexdigest(to_s)
43
57
  end
44
58
 
45
- #:nodoc:
59
+ def env_auth_user
60
+ return unless env_auth
61
+
62
+ env_auth.split(":", 2).first
63
+ end
64
+
65
+ def env_auth_pass
66
+ return unless env_auth
67
+ return unless env_auth.include?(":")
68
+
69
+ env_auth.split(":", 2).last
70
+ end
71
+
72
+ def env_auth
73
+ @env_auth ||= ENV["GEMSTASH_#{host_for_env}"]
74
+ end
75
+
76
+ def host_for_env
77
+ host.upcase.gsub(".", "__").gsub("-", "___")
78
+ end
79
+
80
+ # :nodoc:
46
81
  class GemName
47
82
  def initialize(upstream, gem_name)
48
83
  @upstream = upstream
@@ -1,4 +1,6 @@
1
- #:nodoc:
1
+ # frozen_string_literal: true
2
+
3
+ # :nodoc:
2
4
  module Gemstash
3
- VERSION = "1.0.0.pre.1"
5
+ VERSION = "2.5.0"
4
6
  end
data/lib/gemstash/web.rb CHANGED
@@ -1,13 +1,17 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "sinatra/base"
2
4
  require "json"
3
5
  require "gemstash"
4
6
 
5
7
  module Gemstash
6
- #:nodoc:
8
+ # :nodoc:
7
9
  class Web < Sinatra::Base
8
- def initialize(gemstash_env: nil, http_client_builder: nil)
9
- @gemstash_env = gemstash_env || Gemstash::Env.new
10
- @http_client_builder = http_client_builder || Gemstash::HTTPClient
10
+ ruby2_keywords def initialize(options = {})
11
+ raise ArgumentError unless options.is_a?(Hash)
12
+
13
+ @gemstash_env = options[:gemstash_env] || Gemstash::Env.new
14
+ @http_client_builder = options[:http_client_builder] || Gemstash::HTTPClient
11
15
  Gemstash::Env.current = @gemstash_env
12
16
  super()
13
17
  end
@@ -26,6 +30,11 @@ module Gemstash
26
30
  body JSON.dump("error" => "Not found", "code" => 404)
27
31
  end
28
32
 
33
+ error GemPusher::ExistingVersionError do
34
+ status 422
35
+ body JSON.dump("error" => "Version already exists", "code" => 422)
36
+ end
37
+
29
38
  get "/" do
30
39
  @gem_source.serve_root
31
40
  end
@@ -46,10 +55,6 @@ module Gemstash
46
55
  @gem_source.serve_yank
47
56
  end
48
57
 
49
- put "/api/v1/gems/unyank" do
50
- @gem_source.serve_unyank
51
- end
52
-
53
58
  post "/api/v1/add_spec.json" do
54
59
  @gem_source.serve_add_spec_json
55
60
  end