gem_mirror 0.1.0.pre

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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 9dc1e0fb7c8d5bee7140b693cb630b95b69f5e9ffb23fe31478759a311748c11
4
+ data.tar.gz: 909b09e6a4dab1184d83dde3004376491c5066ef54d9c8f55b036a90a7148c4c
5
+ SHA512:
6
+ metadata.gz: 359b87727794e927bb4c9332dbae112bfe01e7bd578a99f574d4a0ad58d7a5749897d8975c7f8fff0637b8084806acd3390266191b0392d95c589bb12c4486a2
7
+ data.tar.gz: 376d68180e4fa30da16ae889952c43254be87e7d34ddefca526cad22085c6bdd5d72c8490072f528c23c5a85d8ec439068a0d5a97ea6d43f4ed2e19ad625916b
data/CHANGELOG.md ADDED
@@ -0,0 +1,7 @@
1
+ ## 0.1.0.pre
2
+
3
+ ### Changes
4
+
5
+ - Refactoring and bringing code up to date with modern Ruby
6
+ - Addressed over 250 rubocop recommendations
7
+ - Dropped legacy gem index option
data/LICENSE.txt ADDED
@@ -0,0 +1,19 @@
1
+ Copyright (c) 2013, Yorick Peterse
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ of this software and associated documentation files (the "Software"), to deal
5
+ in the Software without restriction, including without limitation the rights
6
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ copies of the Software, and to permit persons to whom the Software is
8
+ furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in
11
+ all copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,85 @@
1
+ # Gem Mirror
2
+
3
+ gem_mirror is a Ruby application that makes it easy to create your own RubyGems
4
+ mirror without having to stitch together ugly Bash scripts or deal with more
5
+ complex tools such as [Geminabox][geminabox]. Unlike tools such as Geminabox
6
+ and others gem_mirror does mirroring only, it has no authentication and you
7
+ can't upload gems to it. I created this project to improve my knowledge of Ruby
8
+ and Ruby infrastructure.
9
+
10
+ It was forked from the now archived github project which has also moved
11
+ to gitlab [gem-mirror].
12
+
13
+ ## Differences with Geminabox
14
+
15
+ Geminabox is basically a tiny DIY RubyGems that focuses more on uploading your
16
+ own gems (e.g. company specific ones) instead of mirroring an external source.
17
+ gem_mirror however purely focuses on generating a mirror that can be used by
18
+ the RubyGems CLI utility.
19
+
20
+ ## Differences with gem-server
21
+
22
+ RubyGems comes with the command `gem-server` which can be used to serve the
23
+ Gems you have installed on that machine and user. Similar to Geminabox it does
24
+ not provide the means to actually mirror an external source. It's also Rack
25
+ based and uses WEBRick, which isn't exactly the best server around.
26
+
27
+ ## How do I use it?
28
+
29
+ The process of setting up a mirror is fairly easy and can be done in a matter
30
+ of minutes depending on the number of gems you are mirroring and your internet
31
+ speed.
32
+
33
+ The first step is to set up a new, empty mirror. This is done by running the
34
+ `gem_mirror init` command similar to how you initialize a new Git repository:
35
+
36
+ $ gem_mirror init /srv/http/mirror.com/
37
+
38
+ Once created, you should edit the main configuration file named `config.rb`.
39
+ This configuration file specifies what sources to mirror (you can mirror
40
+ multiple ones!), which gems, etc.
41
+
42
+ Once configured you can pull all the gems by running the following command:
43
+
44
+ $ gem_mirror update
45
+
46
+ If your configuration file is not located in the current directory you can
47
+ specify the path to it using the `-c` or `--config` option. The process of
48
+ downloading all the Gems takes a while and can use quite a bit of memory so
49
+ make sure you have plenty of time and RAM available.
50
+
51
+ Once all the Gems have been downloaded you'll need to generate an index of all
52
+ the installed files. This can be done as following:
53
+
54
+ $ gem_mirror index
55
+
56
+ Last but not least you may want to generate a list of SHA512 checksums. Although
57
+ RubyGems doesn't use these they might come in handy when verifying Gems (e.g.
58
+ when RubyGems gets hacked again). This can be done using the following command:
59
+
60
+ $ gem_mirror checksum
61
+
62
+ If you want to do all this automatically (which is recommended) you can use a
63
+ simple Cronjob like the following one:
64
+
65
+ @hourly cd /srv/http/mirror.com && gem_mirror update && gem_mirror index
66
+
67
+ ## Requirements
68
+
69
+ * Ruby 2.5.0 or newer
70
+ * Enough space to store Gems
71
+
72
+ ## Installation
73
+
74
+ Assuming RubyGems isn't down you can install the Gem as following:
75
+
76
+ $ gem install gem_mirror
77
+
78
+ ## License
79
+
80
+ All source code in this repository is licensed under the MIT license unless
81
+ specified otherwise. A copy of this license can be found in the file "LICENSE"
82
+ in the root directory of this repository.
83
+
84
+ [geminabox]: https://github.com/geminabox/geminabox
85
+ [gem-mirror]: https://gitlab.com/yorickpeterse/gem-mirror
data/exe/gem_mirror ADDED
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ # frozen_string_literal: true
4
+
5
+ require File.expand_path("../../lib/gem_mirror", __FILE__)
6
+
7
+ GemMirror::CLI.options.parse
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "lib/gem_mirror/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "gem_mirror"
7
+ s.version = GemMirror::VERSION
8
+ s.platform = Gem::Platform::RUBY
9
+ s.required_ruby_version = Gem::Requirement.new(">= 2.5.0")
10
+ s.authors = ["Yorick Peterse", "Patrick Callahan"]
11
+ s.description = <<~DESCRIPTION
12
+ gem-mirror is a tool for creating and managing a private
13
+ mirror of RubyGems. It is also suitable for hosting private
14
+ gems.
15
+ DESCRIPTION
16
+
17
+ s.email = ["pmc@patrickcallahan.com"]
18
+ s.files = Dir["{assets,config,lib,template}/**/*"] + ["gem_mirror.gemspec"]
19
+ s.files += %w[CHANGELOG.md LICENSE.txt README.md]
20
+ s.require_paths = ["lib"]
21
+ s.bindir = "exe"
22
+ s.executables = %w[gem_mirror]
23
+ s.extra_rdoc_files = %w[CHANGELOG.md LICENSE.txt README.md]
24
+ s.homepage = "https://github.com/dirtyharrycallahan/gem_mirror"
25
+ s.licenses = ["MIT"]
26
+ s.summary = %q{A tool for creating and managing a private rubygems mirror.}
27
+
28
+ s.rdoc_options += [
29
+ "--title", "gem_mirror - A Tool for Managing a Private Gem Repository",
30
+ "--main", "README.md",
31
+ "--line-numbers",
32
+ "--inline-source",
33
+ "--quiet"
34
+ ]
35
+ s.metadata["allowed_push_host"] = "https://rubygems.org"
36
+ s.metadata["changelog_uri"] = "https://github.com/dirtyharrycallahan/gem_mirror/blob/master/CHANGELOG.md"
37
+ s.metadata["homepage_uri"] = s.homepage
38
+ s.metadata["source_code_uri"] = s.homepage
39
+ s.metadata["bug_tracker_uri"] = "https://github.com/dirtyharrycallahan/gem_mirror/issues"
40
+
41
+ s.add_runtime_dependency("confstruct", "~> 1.0", "< 2")
42
+ s.add_runtime_dependency("httpclient", "~> 2.8", "< 3")
43
+ s.add_runtime_dependency("slop", "= 3.6")
44
+
45
+ s.add_development_dependency("bundler", ">= 1.17.3", "< 3.0")
46
+ s.add_development_dependency("rake", ">= 13.0.0", "< 14")
47
+ s.add_development_dependency("rake-manifest", "~> 0.2.0")
48
+ s.add_development_dependency("redcarpet", "~> 3.5.0")
49
+ s.add_development_dependency("rspec", "~> 3.2")
50
+ s.add_development_dependency("rubocop", "= 1.16.0")
51
+ s.add_development_dependency("rubocop-performance", "= 1.11.3")
52
+ s.add_development_dependency("rubocop-rake", "= 0.5.1")
53
+ s.add_development_dependency("rubocop-rspec", "= 2.3.0")
54
+ s.add_development_dependency("yard", "~> 0.9.0")
55
+ end
data/lib/gem_mirror.rb ADDED
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rubygems"
4
+ require "rubygems/user_interaction"
5
+ require "rubygems/indexer"
6
+ require "fileutils"
7
+ require "digest/sha2"
8
+ require "confstruct"
9
+ require "slop"
10
+ require "zlib"
11
+ require "httpclient"
12
+ require "logger"
13
+ require "stringio"
14
+
15
+ $LOAD_PATH.unshift(File.expand_path(__dir__)) unless $LOAD_PATH.include?(File.expand_path(__dir__))
16
+
17
+ require_relative "gem_mirror/version"
18
+ require_relative "gem_mirror/configuration"
19
+ require_relative "gem_mirror/gem"
20
+ require_relative "gem_mirror/source"
21
+ require_relative "gem_mirror/mirror_directory"
22
+ require_relative "gem_mirror/mirror_file"
23
+ require_relative "gem_mirror/versions_file"
24
+ require_relative "gem_mirror/versions_fetcher"
25
+ require_relative "gem_mirror/gems_fetcher"
26
+
27
+ require_relative "gem_mirror/cli"
28
+ require_relative "gem_mirror/cli/checksum"
29
+ require_relative "gem_mirror/cli/index"
30
+ require_relative "gem_mirror/cli/init"
31
+ require_relative "gem_mirror/cli/update"
@@ -0,0 +1,67 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Namespace for GemMirror
4
+ module GemMirror
5
+ # Namespace for classes and modules that handle the command line interface
6
+ module CLI
7
+ ##
8
+ # Hash containing the default Slop options.
9
+ #
10
+ # @return [Hash]
11
+ #
12
+ SLOP_OPTIONS = {
13
+ strict: true,
14
+ banner: "Usage: gem_mirror [COMMAND] [OPTIONS]"
15
+ }.freeze
16
+
17
+ ##
18
+ # @return [Slop]
19
+ #
20
+ def self.options
21
+ @options ||= default_options
22
+ end
23
+
24
+ ##
25
+ # Loads the specified configuration file or displays an error if it doesn't
26
+ # exist.
27
+ #
28
+ # @param [String] config_file
29
+ # @return [GemMirror::Configuration]
30
+ #
31
+ def self.load_configuration(config_file)
32
+ config_file ||= Configuration.default_configuration_file
33
+ config_file = File.expand_path(config_file, Dir.pwd)
34
+
35
+ abort "The configuration file #{config_file} does not exist" unless File.file?(config_file)
36
+
37
+ require(config_file)
38
+ end
39
+
40
+ ##
41
+ # @return [Slop]
42
+ #
43
+ def self.default_options
44
+ Slop.new(SLOP_OPTIONS.dup) do
45
+ separator "\nOptions:\n"
46
+
47
+ on :h, :help, "Shows this help message" do
48
+ puts self
49
+ exit
50
+ end
51
+
52
+ on :v, :version, "Shows the current version" do
53
+ puts CLI.version_information
54
+ end
55
+ end
56
+ end
57
+
58
+ ##
59
+ # Returns a String containing some platform/version related information.
60
+ #
61
+ # @return [String]
62
+ #
63
+ def self.version_information
64
+ "gem_mirror v#{GemMirror::VERSION} on #{RUBY_DESCRIPTION}"
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ GemMirror::CLI.options.command "checksum" do
4
+ banner "Usage: gem_mirror checksum [OPTIONS]"
5
+ description "Generates SHA512 checksums of all gems"
6
+ separator "\nOptions:\n"
7
+
8
+ on :h, :help, "Shows this help message" do
9
+ puts self
10
+ exit
11
+ end
12
+
13
+ on :c=, :config=, "Path to the configuration file"
14
+
15
+ run do |opts, _args|
16
+ GemMirror::CLI.load_configuration(opts[:c])
17
+
18
+ config = GemMirror.configuration
19
+
20
+ unless File.directory?(config.checksums)
21
+ config.logger("The directory #{config.checksums} does not exist")
22
+ abort
23
+ end
24
+
25
+ unless File.directory?(config.destination)
26
+ config.logger("The directory #{config.destination} does not exist")
27
+ abort
28
+ end
29
+
30
+ Dir[File.join(config.gems_directory, "*.gem")].each do |gem|
31
+ basename = File.basename(gem)
32
+ name = "#{basename}.sha512"
33
+
34
+ config.logger.info("Creating checksum for #{basename}")
35
+
36
+ hash = Digest::SHA512.hexdigest(File.read(gem))
37
+ handle = File.open(File.join(config.checksums, name), "w")
38
+
39
+ handle.write(hash)
40
+ handle.close
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ GemMirror::CLI.options.command "index" do
4
+ banner "Usage: gem_mirror index [OPTIONS]"
5
+ description "Indexes a list of Gems"
6
+ separator "\nOptions:\n"
7
+
8
+ on :h, :help, "Shows this help message" do
9
+ puts self
10
+ exit
11
+ end
12
+
13
+ on :c=, :config=, "Path to the configuration file"
14
+
15
+ run do |opts, _args|
16
+ GemMirror::CLI.load_configuration(opts[:c])
17
+
18
+ config = GemMirror.configuration
19
+
20
+ unless File.directory?(config.destination)
21
+ config.logger.error("The directory #{config.destination} does not exist")
22
+ abort
23
+ end
24
+
25
+ indexer = Gem::Indexer.new(config.destination, build_legacy: opts[:l])
26
+
27
+ config.logger.info("Generating Indexes")
28
+
29
+ indexer.generate_index
30
+ end
31
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ GemMirror::CLI.options.command "init" do
4
+ banner "Usage: gem_mirror init [DIRECTORY] [OPTIONS]"
5
+ description "Sets up a new mirror"
6
+ separator "\nOptions:\n"
7
+
8
+ on :h, :help, "Shows this help message" do
9
+ puts self
10
+ exit
11
+ end
12
+
13
+ run do |_opts, args|
14
+ directory = File.expand_path(args[0] || Dir.pwd)
15
+ template = GemMirror::Configuration.template_directory
16
+
17
+ Dir.mkdir(directory) unless File.directory?(directory)
18
+
19
+ FileUtils.cp_r(File.join(template, "."), directory)
20
+
21
+ puts "Initialized empty mirror in #{directory}"
22
+ end
23
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ GemMirror::CLI.options.command "update" do
4
+ banner "Usage: gem_mirror update [OPTIONS]"
5
+ description "Updates the list of Gems"
6
+ separator "\nOptions:\n"
7
+
8
+ on :h, :help, "Shows this help message" do
9
+ puts self
10
+ exit
11
+ end
12
+
13
+ on :c=, :config=, "Path to the configuration file"
14
+
15
+ run do |opts, _args|
16
+ GemMirror::CLI.load_configuration(opts[:c])
17
+
18
+ GemMirror.configuration.sources.each do |source|
19
+ versions = GemMirror::VersionsFetcher.new(source).fetch
20
+ gems = GemMirror::GemsFetcher.new(source, versions)
21
+
22
+ gems.fetch
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,140 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Namespace for GemMirror
4
+ module GemMirror
5
+ ##
6
+ # @return [GemMirror::Configuration]
7
+ #
8
+ def self.configuration
9
+ @configuration ||= Configuration.new
10
+ end
11
+
12
+ ##
13
+ # Configuration class used for storing data about a mirror such as the
14
+ # destination directory, sources, ignored Gems, etc.
15
+ #
16
+ class Configuration < Confstruct::Configuration
17
+ ##
18
+ # @return [Logger]
19
+ #
20
+ def logger
21
+ @logger = Logger.new($stdout,
22
+ progname: "gem_mirror",
23
+ formatter: proc do |severity, datetime, progname, msg|
24
+ d_format = datetime.strftime("%Y%m%dT%H%M%S%z")
25
+ "[#{d_format}] severity=#{severity} prog=#{progname} pid=#{Process.pid} message=#{msg}\n"
26
+ end)
27
+ end
28
+
29
+ ##
30
+ # @return [String]
31
+ #
32
+ def self.template_directory
33
+ File.expand_path("../../template", __dir__)
34
+ end
35
+
36
+ ##
37
+ # @return [String]
38
+ #
39
+ def self.default_configuration_file
40
+ File.expand_path("config.rb", Dir.pwd)
41
+ end
42
+
43
+ ##
44
+ # Returns the name of the directory that contains the quick
45
+ # specification files.
46
+ #
47
+ # @return [String]
48
+ #
49
+ def self.marshal_identifier
50
+ "Marshal.#{marshal_version}"
51
+ end
52
+
53
+ ##
54
+ # Returns the name of the file that contains an index of all the versions.
55
+ #
56
+ # @return [String]
57
+ #
58
+ def self.versions_file
59
+ "specs.#{marshal_version}.gz"
60
+ end
61
+
62
+ ##
63
+ # Returns a String containing the Marshal version.
64
+ #
65
+ # @return [String]
66
+ #
67
+ def self.marshal_version
68
+ "#{Marshal::MAJOR_VERSION}.#{Marshal::MINOR_VERSION}"
69
+ end
70
+
71
+ ##
72
+ # @return [GemMirror::MirrorDirectory]
73
+ #
74
+ def mirror_directory
75
+ @mirror_directory ||= MirrorDirectory.new(gems_directory)
76
+ end
77
+
78
+ ##
79
+ # @return [String]
80
+ #
81
+ def gems_directory
82
+ File.join(destination, "gems")
83
+ end
84
+
85
+ ##
86
+ # Returns a Hash containing various Gems to ignore and their versions.
87
+ #
88
+ # @return [Hash]
89
+ #
90
+ def ignored_gems
91
+ @ignored_gems ||= Hash.new { |hash, key| hash[key] = [] }
92
+ end
93
+
94
+ ##
95
+ # Adds a Gem to the list of Gems to ignore.
96
+ #
97
+ # @param [String] name
98
+ # @param [String] version
99
+ #
100
+ def ignore_gem(name, version)
101
+ ignored_gems[name] ||= []
102
+ ignored_gems[name] << version
103
+ end
104
+
105
+ ##
106
+ # Checks if a Gem should be ignored.
107
+ #
108
+ # @param [String] name
109
+ # @param [String] version
110
+ # @return [TrueClass|FalseClass]
111
+ #
112
+ def ignore_gem?(name, version)
113
+ ignored_gems[name].include?(version)
114
+ end
115
+
116
+ ##
117
+ # Returns a list of sources to mirror.
118
+ #
119
+ # @return [Array]
120
+ #
121
+ def sources
122
+ @sources ||= []
123
+ end
124
+
125
+ ##
126
+ # Adds a new source to mirror.
127
+ #
128
+ # @param [String] name
129
+ # @param [String] url
130
+ # @param [Proc] block
131
+ # @yieldparam [GemMirror::Source] source
132
+ #
133
+ def source(name, url, &block)
134
+ source = Source.new(name, url)
135
+ source.instance_eval(&block)
136
+
137
+ sources << source
138
+ end
139
+ end
140
+ end