gemstash 1.0.0.pre.1-java → 2.6.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.
- 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
|