gemstash 1.0.0.pre.1-java

Sign up to get free protection for your applications and to get access to all the features.
Files changed (59) hide show
  1. checksums.yaml +7 -0
  2. data/CODE_OF_CONDUCT.md +13 -0
  3. data/Gemfile +4 -0
  4. data/LICENSE.txt +21 -0
  5. data/README.md +139 -0
  6. data/Rakefile +35 -0
  7. data/bin/console +14 -0
  8. data/bin/gemstash +3 -0
  9. data/bin/setup +5 -0
  10. data/docs/config.md +136 -0
  11. data/docs/debug.md +24 -0
  12. data/docs/deploy.md +30 -0
  13. data/docs/mirror.md +30 -0
  14. data/docs/multiple_sources.md +68 -0
  15. data/docs/private_gems.md +140 -0
  16. data/docs/reference.md +308 -0
  17. data/exe/gemstash +3 -0
  18. data/gemstash.gemspec +47 -0
  19. data/gemstash.png +0 -0
  20. data/lib/gemstash.rb +26 -0
  21. data/lib/gemstash/authorization.rb +87 -0
  22. data/lib/gemstash/cache.rb +79 -0
  23. data/lib/gemstash/cli.rb +71 -0
  24. data/lib/gemstash/cli/authorize.rb +69 -0
  25. data/lib/gemstash/cli/base.rb +46 -0
  26. data/lib/gemstash/cli/setup.rb +173 -0
  27. data/lib/gemstash/cli/start.rb +52 -0
  28. data/lib/gemstash/cli/status.rb +21 -0
  29. data/lib/gemstash/cli/stop.rb +21 -0
  30. data/lib/gemstash/config.ru +13 -0
  31. data/lib/gemstash/configuration.rb +41 -0
  32. data/lib/gemstash/db.rb +15 -0
  33. data/lib/gemstash/db/authorization.rb +20 -0
  34. data/lib/gemstash/db/dependency.rb +50 -0
  35. data/lib/gemstash/db/rubygem.rb +14 -0
  36. data/lib/gemstash/db/version.rb +51 -0
  37. data/lib/gemstash/dependencies.rb +93 -0
  38. data/lib/gemstash/env.rb +150 -0
  39. data/lib/gemstash/gem_fetcher.rb +50 -0
  40. data/lib/gemstash/gem_pusher.rb +125 -0
  41. data/lib/gemstash/gem_source.rb +37 -0
  42. data/lib/gemstash/gem_source/dependency_caching.rb +40 -0
  43. data/lib/gemstash/gem_source/private_source.rb +139 -0
  44. data/lib/gemstash/gem_source/rack_middleware.rb +22 -0
  45. data/lib/gemstash/gem_source/upstream_source.rb +183 -0
  46. data/lib/gemstash/gem_unyanker.rb +61 -0
  47. data/lib/gemstash/gem_yanker.rb +61 -0
  48. data/lib/gemstash/http_client.rb +77 -0
  49. data/lib/gemstash/logging.rb +93 -0
  50. data/lib/gemstash/migrations/01_gem_dependencies.rb +41 -0
  51. data/lib/gemstash/migrations/02_authorizations.rb +12 -0
  52. data/lib/gemstash/puma.rb +6 -0
  53. data/lib/gemstash/rack_env_rewriter.rb +66 -0
  54. data/lib/gemstash/specs_builder.rb +93 -0
  55. data/lib/gemstash/storage.rb +207 -0
  56. data/lib/gemstash/upstream.rb +65 -0
  57. data/lib/gemstash/version.rb +4 -0
  58. data/lib/gemstash/web.rb +97 -0
  59. metadata +304 -0
@@ -0,0 +1,52 @@
1
+ require "gemstash"
2
+ require "puma/cli"
3
+
4
+ module Gemstash
5
+ class CLI
6
+ # This implements the command line start task to start the Gemstash server:
7
+ # $ gemstash start
8
+ class Start < Gemstash::CLI::Base
9
+ def run
10
+ prepare
11
+ setup_logging
12
+ store_daemonized
13
+ Puma::CLI.new(args, Gemstash::Logging::StreamLogger.puma_events).run
14
+ end
15
+
16
+ private
17
+
18
+ def setup_logging
19
+ return unless daemonize?
20
+ Gemstash::Logging.setup_logger(gemstash_env.base_file("server.log"))
21
+ end
22
+
23
+ def store_daemonized
24
+ Gemstash::Env.daemonized = daemonize?
25
+ end
26
+
27
+ def daemonize?
28
+ @cli.options[:daemonize]
29
+ end
30
+
31
+ def puma_config
32
+ File.expand_path("../../puma.rb", __FILE__)
33
+ end
34
+
35
+ def args
36
+ config_args + pidfile_args + daemonize_args
37
+ end
38
+
39
+ def config_args
40
+ ["--config", puma_config]
41
+ end
42
+
43
+ def daemonize_args
44
+ if daemonize?
45
+ ["--daemon"]
46
+ else
47
+ []
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,21 @@
1
+ require "gemstash"
2
+ require "puma/control_cli"
3
+
4
+ module Gemstash
5
+ class CLI
6
+ # This implements the command line status task to check the server status:
7
+ # $ gemstash status
8
+ class Status < Gemstash::CLI::Base
9
+ def run
10
+ prepare
11
+ Puma::ControlCLI.new(args).run
12
+ end
13
+
14
+ private
15
+
16
+ def args
17
+ pidfile_args + %w(status)
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,21 @@
1
+ require "gemstash"
2
+ require "puma/control_cli"
3
+
4
+ module Gemstash
5
+ class CLI
6
+ # This implements the command line stop task to stop the Gemstash server:
7
+ # $ gemstash stop
8
+ class Stop < Gemstash::CLI::Base
9
+ def run
10
+ prepare
11
+ Puma::ControlCLI.new(args).run
12
+ end
13
+
14
+ private
15
+
16
+ def args
17
+ pidfile_args + %w(stop)
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,13 @@
1
+ require "gemstash"
2
+ require "puma/commonlogger"
3
+
4
+ use Rack::Deflater
5
+ use Gemstash::Logging::RackMiddleware
6
+
7
+ if Gemstash::Env.daemonized?
8
+ use Puma::CommonLogger, Gemstash::Logging::StreamLogger.for_stdout
9
+ end
10
+
11
+ use Gemstash::Env::RackMiddleware, Gemstash::Env.current
12
+ use Gemstash::GemSource::RackMiddleware
13
+ run Gemstash::Web.new(gemstash_env: Gemstash::Env.current)
@@ -0,0 +1,41 @@
1
+ require "yaml"
2
+
3
+ module Gemstash
4
+ #:nodoc:
5
+ class Configuration
6
+ DEFAULTS = {
7
+ :cache_type => "memory",
8
+ :base_path => File.expand_path("~/.gemstash"),
9
+ :db_adapter => "sqlite3",
10
+ :bind => "tcp://0.0.0.0:9292",
11
+ :rubygems_url => "https://www.rubygems.org"
12
+ }.freeze
13
+
14
+ DEFAULT_FILE = File.expand_path("~/.gemstash/config.yml").freeze
15
+
16
+ def initialize(file: nil, config: nil)
17
+ if config
18
+ @config = DEFAULTS.merge(config).freeze
19
+ return
20
+ end
21
+
22
+ file ||= DEFAULT_FILE
23
+
24
+ if File.exist?(file)
25
+ @config = YAML.load_file(file)
26
+ @config = DEFAULTS.merge(@config)
27
+ @config.freeze
28
+ else
29
+ @config = DEFAULTS
30
+ end
31
+ end
32
+
33
+ def default?(key)
34
+ @config[key] == DEFAULTS[key]
35
+ end
36
+
37
+ def [](key)
38
+ @config[key]
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,15 @@
1
+ require "gemstash"
2
+
3
+ module Gemstash
4
+ # Module containing the DB models.
5
+ module DB
6
+ raise "Gemstash::DB cannot be loaded until the Gemstash::Env is available" unless Gemstash::Env.available?
7
+ Sequel::Model.db = Gemstash::Env.current.db
8
+ Sequel::Model.raise_on_save_failure = true
9
+ Sequel::Model.plugin :timestamps, update_on_create: true
10
+ autoload :Authorization, "gemstash/db/authorization"
11
+ autoload :Dependency, "gemstash/db/dependency"
12
+ autoload :Rubygem, "gemstash/db/rubygem"
13
+ autoload :Version, "gemstash/db/version"
14
+ end
15
+ end
@@ -0,0 +1,20 @@
1
+ require "gemstash"
2
+
3
+ module Gemstash
4
+ module DB
5
+ # Sequel model for authorizations table.
6
+ class Authorization < Sequel::Model
7
+ def self.insert_or_update(auth_key, permissions)
8
+ db.transaction do
9
+ record = self[auth_key: auth_key]
10
+
11
+ if record
12
+ record.update(permissions: permissions)
13
+ else
14
+ create(auth_key: auth_key, permissions: permissions)
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,50 @@
1
+ require "gemstash"
2
+
3
+ module Gemstash
4
+ module DB
5
+ # Sequel model for dependencies table.
6
+ class Dependency < Sequel::Model
7
+ def self.insert_by_spec(version_id, spec)
8
+ spec.runtime_dependencies.each do |dep|
9
+ requirements = dep.requirement.requirements
10
+ requirements = requirements.map {|r| "#{r.first} #{r.last}" }
11
+ requirements = requirements.join(", ")
12
+ create(version_id: version_id,
13
+ rubygem_name: dep.name,
14
+ requirements: requirements)
15
+ end
16
+ end
17
+
18
+ def self.fetch(gems)
19
+ results = db["
20
+ SELECT rubygem.name,
21
+ version.number, version.platform,
22
+ dependency.rubygem_name, dependency.requirements
23
+ FROM rubygems rubygem
24
+ JOIN versions version
25
+ ON version.rubygem_id = rubygem.id
26
+ LEFT JOIN dependencies dependency
27
+ ON dependency.version_id = version.id
28
+ WHERE rubygem.name IN ?
29
+ AND version.indexed = ?", gems.to_a, true].to_a
30
+ results.group_by {|r| r[:name] }.each do |gem, rows|
31
+ requirements = rows.group_by {|r| [r[:number], r[:platform]] }
32
+
33
+ value = requirements.map do |version, r|
34
+ deps = r.map {|x| [x[:rubygem_name], x[:requirements]] }
35
+ deps = [] if deps.size == 1 && deps.first.first.nil?
36
+
37
+ {
38
+ :name => gem,
39
+ :number => version.first,
40
+ :platform => version.last,
41
+ :dependencies => deps
42
+ }
43
+ end
44
+
45
+ yield(gem, value)
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,14 @@
1
+ require "gemstash"
2
+
3
+ module Gemstash
4
+ module DB
5
+ # Sequel model for rubygems table.
6
+ class Rubygem < Sequel::Model
7
+ def self.find_or_insert(spec)
8
+ record = self[name: spec.name]
9
+ return record.id if record
10
+ new(name: spec.name).tap(&:save).id
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,51 @@
1
+ require "gemstash"
2
+
3
+ module Gemstash
4
+ module DB
5
+ # Sequel model for versions table.
6
+ class Version < Sequel::Model
7
+ many_to_one :rubygem
8
+
9
+ def deindex
10
+ update(indexed: false)
11
+ end
12
+
13
+ def reindex
14
+ update(indexed: true)
15
+ end
16
+
17
+ # This converts to the format used by /private/specs.4.8.gz
18
+ def to_spec
19
+ [rubygem.name, Gem::Version.new(number), platform]
20
+ end
21
+
22
+ def self.for_spec_collection(prerelease: false)
23
+ where(indexed: true, prerelease: prerelease).association_join(:rubygem)
24
+ end
25
+
26
+ def self.find_by_spec(gem_id, spec)
27
+ self[rubygem_id: gem_id,
28
+ number: spec.version.to_s,
29
+ platform: spec.platform.to_s]
30
+ end
31
+
32
+ def self.find_by_full_name(full_name)
33
+ result = self[full_name: full_name]
34
+ return result if result
35
+ # Try again with the default platform, in case it is implied
36
+ self[full_name: "#{full_name}-ruby"]
37
+ end
38
+
39
+ def self.insert_by_spec(gem_id, spec)
40
+ gem_name = Gemstash::DB::Rubygem[gem_id].name
41
+ new(rubygem_id: gem_id,
42
+ number: spec.version.to_s,
43
+ platform: spec.platform.to_s,
44
+ full_name: "#{gem_name}-#{spec.version}-#{spec.platform}",
45
+ storage_id: spec.full_name,
46
+ indexed: true,
47
+ prerelease: spec.version.prerelease?).tap(&:save).id
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,93 @@
1
+ require "cgi"
2
+ require "set"
3
+
4
+ module Gemstash
5
+ #:nodoc:
6
+ class Dependencies
7
+ def self.for_private
8
+ new(scope: "private", db_model: Gemstash::DB::Dependency)
9
+ end
10
+
11
+ def self.for_upstream(upstream, http_client)
12
+ new(scope: "upstream/#{upstream}", http_client: http_client)
13
+ end
14
+
15
+ def initialize(scope: nil, http_client: nil, db_model: nil)
16
+ @scope = scope
17
+ @http_client = http_client
18
+ @db_model = db_model
19
+ end
20
+
21
+ def fetch(gems)
22
+ Fetcher.new(gems, @scope, @http_client, @db_model).fetch
23
+ end
24
+
25
+ #:nodoc:
26
+ class Fetcher
27
+ include Gemstash::Env::Helper
28
+ include Gemstash::Logging
29
+
30
+ def initialize(gems, scope, http_client, db_model)
31
+ @gems = Set.new(gems)
32
+ @scope = scope
33
+ @http_client = http_client
34
+ @db_model = db_model
35
+ @dependencies = []
36
+ end
37
+
38
+ def fetch
39
+ fetch_from_cache
40
+ fetch_from_database
41
+ fetch_from_web
42
+ cache_missing
43
+ @dependencies
44
+ end
45
+
46
+ private
47
+
48
+ def done?
49
+ @gems.empty?
50
+ end
51
+
52
+ def fetch_from_cache
53
+ gemstash_env.cache.dependencies(@scope, @gems) do |gem, value|
54
+ @gems.delete(gem)
55
+ @dependencies += value
56
+ end
57
+ end
58
+
59
+ def fetch_from_database
60
+ return if done?
61
+ return unless @db_model
62
+ log.info "Querying dependencies: #{@gems.to_a.join(", ")}"
63
+
64
+ @db_model.fetch(@gems) do |gem, value|
65
+ @gems.delete(gem)
66
+ gemstash_env.cache.set_dependency(@scope, gem, value)
67
+ @dependencies += value
68
+ end
69
+ end
70
+
71
+ def fetch_from_web
72
+ return if done?
73
+ return unless @http_client
74
+ log.info "Fetching dependencies: #{@gems.to_a.join(", ")}"
75
+ gems_param = @gems.map {|gem| CGI.escape(gem) }.join(",")
76
+ fetched = @http_client.get("api/v1/dependencies?gems=#{gems_param}")
77
+ fetched = Marshal.load(fetched).group_by {|r| r[:name] }
78
+
79
+ fetched.each do |gem, result|
80
+ @gems.delete(gem)
81
+ gemstash_env.cache.set_dependency(@scope, gem, result)
82
+ @dependencies += result
83
+ end
84
+ end
85
+
86
+ def cache_missing
87
+ @gems.each do |gem|
88
+ gemstash_env.cache.set_dependency(@scope, gem, [])
89
+ end
90
+ end
91
+ end
92
+ end
93
+ end
@@ -0,0 +1,150 @@
1
+ require "gemstash"
2
+ require "dalli"
3
+ require "fileutils"
4
+ require "sequel"
5
+ require "uri"
6
+
7
+ module Gemstash
8
+ # Storage for application-wide variables and configuration.
9
+ class Env
10
+ # The Gemstash::Env must be set before being retreived via
11
+ # Gemstash::Env.current. This error is thrown when that is not honored.
12
+ class EnvNotSetError < StandardError
13
+ end
14
+
15
+ # Little module to provide easy access to the current Gemstash::Env.
16
+ module Helper
17
+ private
18
+
19
+ def gemstash_env
20
+ Gemstash::Env.current
21
+ end
22
+ end
23
+
24
+ # Rack middleware to set the Gemstash::Env for the app.
25
+ class RackMiddleware
26
+ def initialize(app, gemstash_env)
27
+ @app = app
28
+ @gemstash_env = gemstash_env
29
+ end
30
+
31
+ def call(env)
32
+ env["gemstash.env"] = @gemstash_env
33
+ @app.call(env)
34
+ end
35
+ end
36
+
37
+ def initialize(config = nil, cache: nil, db: nil)
38
+ @config = config
39
+ @cache = cache
40
+ @db = db
41
+ end
42
+
43
+ def self.available?
44
+ !Thread.current[:gemstash_env].nil?
45
+ end
46
+
47
+ def self.current
48
+ raise EnvNotSetError unless Thread.current[:gemstash_env]
49
+ Thread.current[:gemstash_env]
50
+ end
51
+
52
+ def self.current=(value)
53
+ Thread.current[:gemstash_env] = value
54
+ end
55
+
56
+ def self.daemonized?
57
+ if @daemonized.nil?
58
+ raise "Daemonized hasn't been set yet!"
59
+ else
60
+ @daemonized
61
+ end
62
+ end
63
+
64
+ def self.daemonized=(value)
65
+ value = false if value.nil?
66
+ @daemonized = value
67
+ end
68
+
69
+ def config
70
+ @config ||= Gemstash::Configuration.new
71
+ end
72
+
73
+ def config=(value)
74
+ reset
75
+ @config = value
76
+ end
77
+
78
+ def reset
79
+ @config = nil
80
+ @cache = nil
81
+ @cache_client = nil
82
+ @db = nil
83
+ end
84
+
85
+ def base_path
86
+ dir = config[:base_path]
87
+
88
+ if config.default?(:base_path)
89
+ FileUtils.mkpath(dir) unless Dir.exist?(dir)
90
+ else
91
+ raise "Base path '#{dir}' is not writable" unless File.writable?(dir)
92
+ end
93
+
94
+ dir
95
+ end
96
+
97
+ def base_file(path)
98
+ File.join(base_path, path)
99
+ end
100
+
101
+ def rackup
102
+ File.expand_path("../config.ru", __FILE__)
103
+ end
104
+
105
+ def db
106
+ @db ||= begin
107
+ case config[:db_adapter]
108
+ when "sqlite3"
109
+ db_path = base_file("gemstash.db")
110
+
111
+ if RUBY_PLATFORM == "java"
112
+ db = Sequel.connect("jdbc:sqlite:#{db_path}", max_connections: 1)
113
+ else
114
+ db = Sequel.connect("sqlite://#{URI.escape(db_path)}", max_connections: 1)
115
+ end
116
+ when "postgres"
117
+ db = Sequel.connect(config[:db_url])
118
+ else
119
+ raise "Unsupported DB adapter: '#{config[:db_adapter]}'"
120
+ end
121
+
122
+ Gemstash::Env.migrate(db)
123
+ db
124
+ end
125
+ end
126
+
127
+ def self.migrate(db)
128
+ Sequel.extension :migration
129
+ migrations_dir = File.expand_path("../migrations", __FILE__)
130
+ Sequel::Migrator.run(db, migrations_dir, :use_transactions => true)
131
+ end
132
+
133
+ def cache
134
+ @cache ||= Gemstash::Cache.new(cache_client)
135
+ end
136
+
137
+ def cache_client
138
+ @cache_client ||= begin
139
+ case config[:cache_type]
140
+ when "memory"
141
+ Gemstash::LruReduxClient.new
142
+ when "memcached"
143
+ Dalli::Client.new(config[:memcached_servers])
144
+ else
145
+ raise "Invalid cache client: '#{config[:cache_type]}'"
146
+ end
147
+ end
148
+ end
149
+ end
150
+ end