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
data/exe/gemstash ADDED
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env ruby
2
+ require "gemstash/cli"
3
+ Gemstash::CLI.start
data/gemstash.gemspec ADDED
@@ -0,0 +1,47 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path("../lib", __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require "gemstash/version"
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "gemstash"
8
+ spec.version = Gemstash::VERSION
9
+ spec.authors = ["Andre Arko"]
10
+ spec.email = ["andre@arko.net"]
11
+ spec.platform = "java" if RUBY_PLATFORM == "java"
12
+
13
+ spec.summary = "A place to stash gems you'll need"
14
+ spec.description = "Gemstash acts as a local RubyGems server, caching \
15
+ copies of gems from RubyGems.org automatically, and eventually letting \
16
+ you push your own private gems as well."
17
+ spec.homepage = "https://github.com/bundler/gemstash"
18
+ spec.license = "MIT"
19
+
20
+ spec.files = `git ls-files -z`.split("\x0").reject {|f|
21
+ f.match(%r{^(test|spec|features)/})
22
+ }
23
+ spec.bindir = "exe"
24
+ spec.executables = spec.files.grep(%r{^exe/}) {|f| File.basename(f) }
25
+ spec.require_paths = ["lib"]
26
+
27
+ spec.add_runtime_dependency "dalli", "~> 2.7"
28
+ spec.add_runtime_dependency "lru_redux", "~> 1.1"
29
+ spec.add_runtime_dependency "puma", "~> 2.14"
30
+ spec.add_runtime_dependency "sequel", "~> 4.26"
31
+ spec.add_runtime_dependency "sinatra", "~> 1.4"
32
+ spec.add_runtime_dependency "thor", "~> 0.19"
33
+ spec.add_runtime_dependency "faraday", "~> 0.9"
34
+ spec.add_runtime_dependency "faraday_middleware", "~> 0.10"
35
+
36
+ if RUBY_PLATFORM == "java"
37
+ spec.add_runtime_dependency "jdbc-sqlite3", "~> 3.8"
38
+ else
39
+ spec.add_runtime_dependency "sqlite3", "~> 1.3"
40
+ end
41
+
42
+ spec.add_development_dependency "bundler", "~> 1.10"
43
+ spec.add_development_dependency "rack-test", "~> 0.6"
44
+ spec.add_development_dependency "rake", "~> 10.0"
45
+ spec.add_development_dependency "rspec", "~> 3.3"
46
+ spec.add_development_dependency "rubocop", "~> 0.34"
47
+ end
data/gemstash.png ADDED
Binary file
data/lib/gemstash.rb ADDED
@@ -0,0 +1,26 @@
1
+ #:nodoc:
2
+ module Gemstash
3
+ autoload :Authorization, "gemstash/authorization"
4
+ autoload :DB, "gemstash/db"
5
+ autoload :Cache, "gemstash/cache"
6
+ autoload :CLI, "gemstash/cli"
7
+ autoload :Configuration, "gemstash/configuration"
8
+ autoload :Dependencies, "gemstash/dependencies"
9
+ autoload :Env, "gemstash/env"
10
+ autoload :GemFetcher, "gemstash/gem_fetcher"
11
+ autoload :GemPusher, "gemstash/gem_pusher"
12
+ autoload :GemSource, "gemstash/gem_source"
13
+ autoload :GemUnyanker, "gemstash/gem_unyanker"
14
+ autoload :GemYanker, "gemstash/gem_yanker"
15
+ autoload :HTTPClient, "gemstash/http_client"
16
+ autoload :Logging, "gemstash/logging"
17
+ autoload :LruReduxClient, "gemstash/cache"
18
+ autoload :NotAuthorizedError, "gemstash/authorization"
19
+ autoload :RackEnvRewriter, "gemstash/rack_env_rewriter"
20
+ autoload :SpecsBuilder, "gemstash/specs_builder"
21
+ autoload :Storage, "gemstash/storage"
22
+ autoload :Upstream, "gemstash/upstream"
23
+ autoload :Web, "gemstash/web"
24
+ autoload :WebError, "gemstash/http_client"
25
+ autoload :VERSION, "gemstash/version"
26
+ end
@@ -0,0 +1,87 @@
1
+ require "set"
2
+
3
+ module Gemstash
4
+ # An action was not authorized and should cause the server to send a 401.
5
+ class NotAuthorizedError < StandardError
6
+ end
7
+
8
+ # Authorization mechanism to manipulate private gems.
9
+ class Authorization
10
+ extend Gemstash::Env::Helper
11
+ extend Gemstash::Logging
12
+ VALID_PERMISSIONS = %w(push yank unyank).freeze
13
+
14
+ def self.authorize(auth_key, permissions)
15
+ raise "Authorization key is required!" if auth_key.to_s.strip.empty?
16
+ raise "Permissions are required!" if permissions.to_s.empty?
17
+
18
+ unless permissions == "all"
19
+ permissions.each do |permission|
20
+ unless VALID_PERMISSIONS.include?(permission)
21
+ raise "Invalid permission '#{permission}'"
22
+ end
23
+ end
24
+
25
+ permissions = permissions.join(",")
26
+ end
27
+
28
+ Gemstash::DB::Authorization.insert_or_update(auth_key, permissions)
29
+ gemstash_env.cache.invalidate_authorization(auth_key)
30
+ log.info "Authorization '#{auth_key}' updated with access to '#{permissions}'"
31
+ end
32
+
33
+ def self.remove(auth_key)
34
+ record = Gemstash::DB::Authorization[auth_key: auth_key]
35
+ return unless record
36
+ record.destroy
37
+ gemstash_env.cache.invalidate_authorization(auth_key)
38
+ log.info "Authorization '#{auth_key}' with access to '#{record.permissions}' removed"
39
+ end
40
+
41
+ def self.check(auth_key, permission)
42
+ raise NotAuthorizedError, "Authorization key required" if auth_key.to_s.strip.empty?
43
+ auth = self[auth_key]
44
+ raise NotAuthorizedError, "Authorization key is invalid" unless auth
45
+ raise NotAuthorizedError, "Authorization key doesn't have #{permission} access" unless auth.can?(permission)
46
+ end
47
+
48
+ def self.[](auth_key)
49
+ cached_auth = gemstash_env.cache.authorization(auth_key)
50
+ return cached_auth if cached_auth
51
+ record = Gemstash::DB::Authorization[auth_key: auth_key]
52
+
53
+ if record
54
+ auth = new(record)
55
+ gemstash_env.cache.set_authorization(record.auth_key, auth)
56
+ auth
57
+ end
58
+ end
59
+
60
+ def initialize(record)
61
+ @auth_key = record.auth_key
62
+ @all = record.permissions == "all"
63
+ @permissions = Set.new(record.permissions.split(","))
64
+ end
65
+
66
+ def can?(permission)
67
+ raise "Invalid permission '#{permission}'" unless VALID_PERMISSIONS.include?(permission)
68
+ all? || @permissions.include?(permission)
69
+ end
70
+
71
+ def all?
72
+ @all
73
+ end
74
+
75
+ def push?
76
+ can?("push")
77
+ end
78
+
79
+ def yank?
80
+ can?("yank")
81
+ end
82
+
83
+ def unyank?
84
+ can?("unyank")
85
+ end
86
+ end
87
+ end
@@ -0,0 +1,79 @@
1
+ require "lru_redux"
2
+ require "forwardable"
3
+
4
+ module Gemstash
5
+ # Cache object which knows about what things are cached and what keys to use
6
+ # for them. Under the hood is either a Memcached client via the dalli gem, or
7
+ # an in memory client via the lru_redux gem.
8
+ class Cache
9
+ EXPIRY = 30 * 60
10
+ extend Forwardable
11
+ def_delegators :@client, :flush
12
+
13
+ def initialize(client)
14
+ @client = client
15
+ end
16
+
17
+ def authorization(auth_key)
18
+ @client.get("auths/#{auth_key}")
19
+ end
20
+
21
+ def set_authorization(auth_key, value)
22
+ @client.set("auths/#{auth_key}", value, EXPIRY)
23
+ end
24
+
25
+ def invalidate_authorization(auth_key)
26
+ @client.delete("auths/#{auth_key}")
27
+ end
28
+
29
+ def dependencies(scope, gems)
30
+ key_prefix = "deps/v1/#{scope}/"
31
+ keys = gems.map {|g| "#{key_prefix}#{g}" }
32
+
33
+ @client.get_multi(keys) do |key, value|
34
+ yield(key.sub(key_prefix, ""), value)
35
+ end
36
+ end
37
+
38
+ def set_dependency(scope, gem, value)
39
+ @client.set("deps/v1/#{scope}/#{gem}", value, EXPIRY)
40
+ end
41
+
42
+ def invalidate_gem(scope, gem)
43
+ @client.delete("deps/v1/#{scope}/#{gem}")
44
+ Gemstash::SpecsBuilder.invalidate_stored if scope == "private"
45
+ end
46
+ end
47
+
48
+ # Wrapper around the lru_redux gem to behave like a dalli Memcached client.
49
+ class LruReduxClient
50
+ MAX_SIZE = 500
51
+ EXPIRY = Gemstash::Cache::EXPIRY
52
+ extend Forwardable
53
+ def_delegators :@cache, :delete
54
+ def_delegator :@cache, :[], :get
55
+ def_delegator :@cache, :clear, :flush
56
+
57
+ def initialize
58
+ @cache = LruRedux::TTL::ThreadSafeCache.new MAX_SIZE, EXPIRY
59
+ end
60
+
61
+ def alive!
62
+ true
63
+ end
64
+
65
+ def get_multi(keys)
66
+ keys.each do |key|
67
+ found = true
68
+ # Atomic fetch... don't rely on nil meaning missing
69
+ value = @cache.fetch(key) { found = false }
70
+ next unless found
71
+ yield(key, value)
72
+ end
73
+ end
74
+
75
+ def set(key, value, expiry)
76
+ @cache[key] = value
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,71 @@
1
+ require "gemstash"
2
+ require "thor"
3
+ require "thor/error"
4
+
5
+ module Gemstash
6
+ # Base Command Line Interface class.
7
+ class CLI < Thor
8
+ autoload :Authorize, "gemstash/cli/authorize"
9
+ autoload :Base, "gemstash/cli/base"
10
+ autoload :Setup, "gemstash/cli/setup"
11
+ autoload :Start, "gemstash/cli/start"
12
+ autoload :Status, "gemstash/cli/status"
13
+ autoload :Stop, "gemstash/cli/stop"
14
+
15
+ # Thor::Error for the CLI, which colors the message red.
16
+ class Error < Thor::Error
17
+ def initialize(cli, message)
18
+ super(cli.set_color(message, :red))
19
+ end
20
+ end
21
+
22
+ def self.exit_on_failure?
23
+ true
24
+ end
25
+
26
+ desc "authorize [PERMISSIONS...]", "Add authorizations to push/yank/unyank private gems"
27
+ method_option :remove, :type => :boolean, :default => false, :desc =>
28
+ "Remove an authorization key"
29
+ method_option :config_file, :type => :string, :desc =>
30
+ "Config file to save to"
31
+ method_option :key, :type => :string, :desc =>
32
+ "Authorization key to create/update/delete (optional unless deleting)"
33
+ def authorize(*args)
34
+ Gemstash::CLI::Authorize.new(self, *args).run
35
+ end
36
+
37
+ desc "setup", "Checks for dependencies and does initial setup"
38
+ method_option :redo, :type => :boolean, :default => false, :desc =>
39
+ "Redo configuration"
40
+ method_option :debug, :type => :boolean, :default => false, :desc =>
41
+ "Show detailed errors"
42
+ method_option :config_file, :type => :string, :desc =>
43
+ "Config file to save to"
44
+ def setup
45
+ Gemstash::CLI::Setup.new(self).run
46
+ end
47
+
48
+ desc "start", "Starts your gemstash server"
49
+ method_option :daemonize, :type => :boolean, :default => true, :desc =>
50
+ "Daemonize the server"
51
+ method_option :config_file, :type => :string, :desc =>
52
+ "Config file to load when starting"
53
+ def start
54
+ Gemstash::CLI::Start.new(self).run
55
+ end
56
+
57
+ desc "status", "Check the status of your gemstash server"
58
+ method_option :config_file, :type => :string, :desc =>
59
+ "Config file to load when checking the status"
60
+ def status
61
+ Gemstash::CLI::Status.new(self).run
62
+ end
63
+
64
+ desc "stop", "Stops your gemstash server"
65
+ method_option :config_file, :type => :string, :desc =>
66
+ "Config file to load when stopping"
67
+ def stop
68
+ Gemstash::CLI::Stop.new(self).run
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,69 @@
1
+ require "gemstash"
2
+ require "securerandom"
3
+
4
+ module Gemstash
5
+ class CLI
6
+ # This implements the command line authorize task to authorize users:
7
+ # $ gemstash authorize authorized-key
8
+ class Authorize < Gemstash::CLI::Base
9
+ def run
10
+ prepare
11
+ setup_logging
12
+
13
+ if @cli.options[:remove]
14
+ remove_authorization
15
+ else
16
+ save_authorization
17
+ end
18
+ end
19
+
20
+ private
21
+
22
+ def setup_logging
23
+ Gemstash::Logging.setup_logger(gemstash_env.base_file("server.log"))
24
+ end
25
+
26
+ def remove_authorization
27
+ raise Gemstash::CLI::Error.new(@cli, "To remove individual permissions, you do not need --remove
28
+ Instead just authorize with the new set of permissions") unless @args.empty?
29
+ Gemstash::Authorization.remove(auth_key(false))
30
+ end
31
+
32
+ def save_authorization
33
+ if @args.include?("all")
34
+ raise Gemstash::CLI::Error.new(@cli, "Don't specify permissions to authorize for all")
35
+ end
36
+
37
+ @args.each do |arg|
38
+ unless Gemstash::Authorization::VALID_PERMISSIONS.include?(arg)
39
+ valid = Gemstash::Authorization::VALID_PERMISSIONS.join(", ")
40
+ raise Gemstash::CLI::Error.new(@cli, "Invalid permission '#{arg}'\nValid permissions include: #{valid}")
41
+ end
42
+ end
43
+
44
+ Gemstash::Authorization.authorize(auth_key, permissions)
45
+ end
46
+
47
+ def auth_key(allow_generate = true)
48
+ if @cli.options[:key]
49
+ @cli.options[:key]
50
+ elsif allow_generate
51
+ key = SecureRandom.hex(16)
52
+ key = SecureRandom.hex(16) while Gemstash::Authorization[key]
53
+ @cli.say "Your new key is: #{key}"
54
+ key
55
+ else
56
+ raise Gemstash::CLI::Error.new(@cli, "The --key option is required to remove an authorization key")
57
+ end
58
+ end
59
+
60
+ def permissions
61
+ if @args.empty?
62
+ "all"
63
+ else
64
+ @args
65
+ end
66
+ end
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,46 @@
1
+ require "gemstash"
2
+
3
+ module Gemstash
4
+ class CLI
5
+ # Base class for common functionality for CLI tasks.
6
+ class Base
7
+ include Gemstash::Env::Helper
8
+
9
+ def initialize(cli, *args)
10
+ Gemstash::Env.current = Gemstash::Env.new
11
+ @cli = cli
12
+ @args = args
13
+ end
14
+
15
+ private
16
+
17
+ def prepare
18
+ check_rubygems_version
19
+ store_config
20
+ check_gemstash_version
21
+ end
22
+
23
+ def check_rubygems_version
24
+ @cli.say(@cli.set_color("Rubygems version is too old, " \
25
+ "please update rubygems by running: " \
26
+ "gem update --system", :red)) unless
27
+ Gem::Requirement.new(">= 2.4").satisfied_by?(Gem::Version.new(Gem::VERSION))
28
+ end
29
+
30
+ def store_config
31
+ config = Gemstash::Configuration.new(file: @cli.options[:config_file])
32
+ gemstash_env.config = config
33
+ end
34
+
35
+ def check_gemstash_version
36
+ version = Gem::Version.new(Gemstash::Storage.metadata[:gemstash_version])
37
+ return if Gem::Requirement.new("<= #{Gemstash::VERSION}").satisfied_by?(Gem::Version.new(version))
38
+ raise Gemstash::CLI::Error.new(@cli, "Gemstash version is too old")
39
+ end
40
+
41
+ def pidfile_args
42
+ ["--pidfile", gemstash_env.base_file("puma.pid")]
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,173 @@
1
+ require "gemstash"
2
+ require "fileutils"
3
+ require "yaml"
4
+
5
+ module Gemstash
6
+ class CLI
7
+ # This implements the command line setup task:
8
+ # $ gemstash setup
9
+ class Setup < Gemstash::CLI::Base
10
+ def initialize(cli)
11
+ super
12
+ @config = {}
13
+ end
14
+
15
+ def run
16
+ if setup? && !@cli.options[:redo]
17
+ @cli.say @cli.set_color("Everything is already setup!", :green)
18
+ return
19
+ end
20
+
21
+ check_rubygems_version
22
+ ask_storage
23
+ ask_cache
24
+ ask_database
25
+ check_cache
26
+ check_storage
27
+ check_database
28
+ store_config
29
+ save_metadata
30
+ @cli.say @cli.set_color("You are all setup!", :green)
31
+ end
32
+
33
+ private
34
+
35
+ def config_file
36
+ @cli.options[:config_file] || Gemstash::Configuration::DEFAULT_FILE
37
+ end
38
+
39
+ def setup?
40
+ File.exist?(config_file)
41
+ end
42
+
43
+ def say_current_config(option, label)
44
+ return if gemstash_env.config.default?(option)
45
+ @cli.say "#{label}: #{gemstash_env.config[option]}"
46
+ end
47
+
48
+ def ask_storage
49
+ say_current_config(:base_path, "Current base path")
50
+ path = @cli.ask "Where should files go? [~/.gemstash]", path: true
51
+ path = Gemstash::Configuration::DEFAULTS[:base_path] if path.empty?
52
+ @config[:base_path] = File.expand_path(path)
53
+ end
54
+
55
+ def ask_cache
56
+ say_current_config(:cache_type, "Current cache")
57
+ options = %w(memory memcached)
58
+ cache = nil
59
+
60
+ until cache
61
+ cache = @cli.ask "Cache with what? [MEMORY, memcached]"
62
+ cache = cache.downcase
63
+ cache = "memory" if cache.empty?
64
+ cache = nil unless options.include?(cache)
65
+ end
66
+
67
+ @config[:cache_type] = cache
68
+ ask_memcached_details if cache == "memcached"
69
+ end
70
+
71
+ def ask_memcached_details
72
+ say_current_config(:memcached_servers, "Current Memcached servers")
73
+ servers = @cli.ask "What is the comma separated Memcached servers? [localhost:11211]"
74
+ servers = "localhost:11211" if servers.empty?
75
+ @config[:memcached_servers] = servers
76
+ end
77
+
78
+ def ask_database
79
+ say_current_config(:db_adapter, "Current database adapter")
80
+ options = %w(sqlite3 postgres)
81
+ database = nil
82
+
83
+ until database
84
+ database = @cli.ask "What database adapter? [SQLITE3, postgres]"
85
+ database = database.downcase
86
+ database = "sqlite3" if database.empty?
87
+ database = nil unless options.include?(database)
88
+ end
89
+
90
+ @config[:db_adapter] = database
91
+ ask_postgres_details if database == "postgres"
92
+ end
93
+
94
+ def ask_postgres_details
95
+ say_current_config(:db_url, "Current database url")
96
+
97
+ if RUBY_PLATFORM == "java"
98
+ default_value = "jdbc:postgres:///gemstash"
99
+ else
100
+ default_value = "postgres:///gemstash"
101
+ end
102
+
103
+ url = @cli.ask "Where is the database? [#{default_value}]"
104
+ url = default_value if url.empty?
105
+ @config[:db_url] = url
106
+ end
107
+
108
+ def check_cache
109
+ @cli.say "Checking that the cache is available"
110
+ with_new_config { gemstash_env.cache_client.alive! }
111
+ rescue => e
112
+ say_error "Cache error", e
113
+ raise Gemstash::CLI::Error.new(@cli, "The cache is not available")
114
+ end
115
+
116
+ def check_database
117
+ @cli.say "Checking that the database is available"
118
+ with_new_config { gemstash_env.db.test_connection }
119
+ rescue => e
120
+ say_error "Database error", e
121
+ raise Gemstash::CLI::Error.new(@cli, "The database is not available")
122
+ end
123
+
124
+ def check_storage
125
+ with_new_config do
126
+ dir = gemstash_env.config[:base_path]
127
+
128
+ if Dir.exist?(dir)
129
+ # Do metadata check without using Gemstash::Storage.metadata because
130
+ # we don't want to store metadata just yet
131
+ metadata_file = gemstash_env.base_file("metadata.yml")
132
+ break unless File.exist?(metadata_file)
133
+ version = Gem::Version.new(YAML.load_file(metadata_file)[:gemstash_version])
134
+ break if Gem::Requirement.new("<= #{Gemstash::VERSION}").satisfied_by?(Gem::Version.new(version))
135
+ raise Gemstash::CLI::Error.new(@cli, "The base path already exists with a newer version of Gemstash")
136
+ else
137
+ @cli.say "Creating the file storage path '#{dir}'"
138
+ FileUtils.mkpath(dir)
139
+ end
140
+ end
141
+ end
142
+
143
+ def store_config
144
+ config_dir = File.dirname(config_file)
145
+ FileUtils.mkpath(config_dir) unless Dir.exist?(config_dir)
146
+ File.write(config_file, YAML.dump(@config))
147
+ end
148
+
149
+ def save_metadata
150
+ with_new_config do
151
+ # Touch metadata to ensure it gets written
152
+ Gemstash::Storage.metadata
153
+ end
154
+ end
155
+
156
+ def say_error(title, error)
157
+ return unless @cli.options[:debug]
158
+ @cli.say @cli.set_color("#{title}: #{error}", :red)
159
+
160
+ error.backtrace.each do |line|
161
+ @cli.say @cli.set_color(" #{line}", :red)
162
+ end
163
+ end
164
+
165
+ def with_new_config
166
+ gemstash_env.config = Gemstash::Configuration.new(config: @config)
167
+ yield
168
+ ensure
169
+ gemstash_env.reset
170
+ end
171
+ end
172
+ end
173
+ end