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