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

Sign up to get free protection for your applications and to get access to all the features.
Files changed (99) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +303 -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 +61 -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.6.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