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.
- checksums.yaml +5 -5
- data/CHANGELOG.md +303 -0
- data/exe/gemstash +3 -0
- data/lib/gemstash/api_key_authorization.rb +32 -0
- data/lib/gemstash/authorization.rb +15 -8
- data/lib/gemstash/cache.rb +42 -2
- data/lib/gemstash/cli/authorize.rb +52 -9
- data/lib/gemstash/cli/base.rb +14 -6
- data/lib/gemstash/cli/setup.rb +67 -39
- data/lib/gemstash/cli/start.rb +6 -2
- data/lib/gemstash/cli/status.rb +3 -1
- data/lib/gemstash/cli/stop.rb +4 -1
- data/lib/gemstash/cli.rb +59 -1
- data/lib/gemstash/config.ru +4 -3
- data/lib/gemstash/configuration.rb +61 -8
- data/lib/gemstash/db/authorization.rb +5 -3
- data/lib/gemstash/db/cached_rubygem.rb +20 -0
- data/lib/gemstash/db/dependency.rb +2 -0
- data/lib/gemstash/db/rubygem.rb +3 -0
- data/lib/gemstash/db/upstream.rb +15 -0
- data/lib/gemstash/db/version.rb +25 -2
- data/lib/gemstash/db.rb +5 -0
- data/lib/gemstash/dependencies.rb +6 -2
- data/lib/gemstash/env.rb +44 -13
- data/lib/gemstash/gem_fetcher.rb +5 -3
- data/lib/gemstash/gem_pusher.rb +25 -18
- data/lib/gemstash/gem_source/dependency_caching.rb +4 -4
- data/lib/gemstash/gem_source/private_source.rb +34 -50
- data/lib/gemstash/gem_source/rack_middleware.rb +3 -0
- data/lib/gemstash/gem_source/upstream_source.rb +71 -27
- data/lib/gemstash/gem_source.rb +4 -2
- data/lib/gemstash/gem_yanker.rb +14 -4
- data/lib/gemstash/health.rb +55 -0
- data/lib/gemstash/http_client.rb +15 -5
- data/lib/gemstash/logging.rb +19 -7
- data/lib/gemstash/man/gemstash-authorize.1 +54 -0
- data/lib/gemstash/man/gemstash-authorize.1.txt +52 -0
- data/lib/gemstash/man/gemstash-configuration.5 +186 -0
- data/lib/gemstash/man/gemstash-configuration.5.txt +208 -0
- data/lib/gemstash/man/gemstash-customize.7 +273 -0
- data/lib/gemstash/man/gemstash-customize.7.txt +184 -0
- data/lib/gemstash/man/gemstash-debugging.7 +30 -0
- data/lib/gemstash/man/gemstash-debugging.7.txt +27 -0
- data/lib/gemstash/man/gemstash-deploy.7 +63 -0
- data/lib/gemstash/man/gemstash-deploy.7.txt +57 -0
- data/lib/gemstash/man/gemstash-mirror.7 +34 -0
- data/lib/gemstash/man/gemstash-mirror.7.txt +31 -0
- data/lib/gemstash/man/gemstash-multiple-sources.7 +131 -0
- data/lib/gemstash/man/gemstash-multiple-sources.7.txt +116 -0
- data/lib/gemstash/man/gemstash-private-gems.7 +191 -0
- data/lib/gemstash/man/gemstash-private-gems.7.txt +154 -0
- data/lib/gemstash/man/gemstash-readme.7 +199 -0
- data/lib/gemstash/man/gemstash-readme.7.txt +177 -0
- data/lib/gemstash/man/gemstash-setup.1 +38 -0
- data/lib/gemstash/man/gemstash-setup.1.txt +38 -0
- data/lib/gemstash/man/gemstash-start.1 +23 -0
- data/lib/gemstash/man/gemstash-start.1.txt +24 -0
- data/lib/gemstash/man/gemstash-status.1 +17 -0
- data/lib/gemstash/man/gemstash-status.1.txt +20 -0
- data/lib/gemstash/man/gemstash-stop.1 +17 -0
- data/lib/gemstash/man/gemstash-stop.1.txt +20 -0
- data/lib/gemstash/man/gemstash-version.1 +17 -0
- data/lib/gemstash/man/gemstash-version.1.txt +19 -0
- data/lib/gemstash/migrations/01_gem_dependencies.rb +11 -9
- data/lib/gemstash/migrations/02_authorizations.rb +4 -2
- data/lib/gemstash/migrations/03_cached_gems.rb +26 -0
- data/lib/gemstash/migrations/04_health_tests.rb +10 -0
- data/lib/gemstash/migrations/05_authorization_names.rb +10 -0
- data/lib/gemstash/puma.rb +5 -3
- data/lib/gemstash/rack_env_rewriter.rb +11 -2
- data/lib/gemstash/specs_builder.rb +25 -15
- data/lib/gemstash/storage.rb +175 -32
- data/lib/gemstash/upstream.rb +43 -8
- data/lib/gemstash/version.rb +4 -2
- data/lib/gemstash/web.rb +13 -8
- data/lib/gemstash.rb +6 -2
- metadata +135 -110
- data/.gitignore +0 -10
- data/.rspec +0 -2
- data/.rubocop-bundler.yml +0 -92
- data/.rubocop-relax.yml +0 -11
- data/.rubocop.yml +0 -8
- data/.travis.yml +0 -20
- data/Gemfile +0 -4
- data/README.md +0 -139
- data/Rakefile +0 -35
- data/bin/console +0 -14
- data/bin/gemstash +0 -3
- data/bin/setup +0 -5
- data/docs/config.md +0 -136
- data/docs/debug.md +0 -24
- data/docs/deploy.md +0 -30
- data/docs/mirror.md +0 -30
- data/docs/multiple_sources.md +0 -68
- data/docs/private_gems.md +0 -140
- data/docs/reference.md +0 -308
- data/gemstash.gemspec +0 -47
- data/gemstash.png +0 -0
- 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
|
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,
|
4
|
-
bind
|
5
|
-
workers
|
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
|
-
|
37
|
-
@rack_env["
|
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
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
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
|
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 @
|
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
|
data/lib/gemstash/storage.rb
CHANGED
@@ -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
|
-
|
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
|
14
|
-
#
|
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
|
-
|
41
|
-
|
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.
|
71
|
+
YAML.safe_load_file(file, permitted_classes: [Symbol])
|
45
72
|
end
|
46
73
|
|
47
74
|
private
|
48
75
|
|
49
|
-
def
|
76
|
+
def check_storage_version
|
50
77
|
version = Gemstash::Storage.metadata[:storage_version]
|
51
78
|
return if version <= Gemstash::Storage::VERSION
|
52
|
-
|
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
|
-
|
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
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
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
|
-
|
265
|
+
self
|
136
266
|
ensure
|
137
267
|
reset
|
138
268
|
end
|
139
269
|
|
140
270
|
private
|
141
271
|
|
142
|
-
def
|
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
|
-
|
145
|
-
|
283
|
+
|
284
|
+
@properties = YAML.safe_load_file(properties_filename, permitted_classes: [Symbol]) || {}
|
285
|
+
check_resource_version
|
146
286
|
end
|
147
287
|
|
148
|
-
def
|
149
|
-
version = @properties[:
|
150
|
-
return if version <= Gemstash::
|
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::
|
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
|
-
|
163
|
-
|
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 = {
|
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
|
-
|
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
|
|
data/lib/gemstash/upstream.rb
CHANGED
@@ -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
|
-
|
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, :
|
14
|
+
def_delegators :@uri, :scheme, :host, :to_s
|
11
15
|
|
12
16
|
def initialize(upstream, user_agent: nil)
|
13
|
-
|
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
|
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
|
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?
|
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
|
-
|
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
|
data/lib/gemstash/version.rb
CHANGED
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
|
-
|
8
|
+
# :nodoc:
|
7
9
|
class Web < Sinatra::Base
|
8
|
-
def initialize(
|
9
|
-
|
10
|
-
|
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
|