private-gem-server 0.1.0
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 +7 -0
- data/LICENSE.txt +22 -0
- data/README.md +60 -0
- data/lib/private-gem-server.rb +1 -0
- data/lib/private_gem_server/sanity.rb +46 -0
- data/lib/private_gem_server/scanner.rb +21 -0
- data/lib/private_gem_server/server.rb +7 -0
- data/lib/private_gem_server/source/git.rb +101 -0
- data/lib/private_gem_server/source.rb +60 -0
- data/lib/private_gem_server/sources.rb +17 -0
- data/lib/private_gem_server/version.rb +3 -0
- data/lib/private_gem_server.rb +34 -0
- metadata +84 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 8eb42e9887adca8308761d745ca5e321a10ff98c
|
4
|
+
data.tar.gz: 0107f405a92edf81010969e4f9204da0577c2d1b
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 7eee56925860a2eadfdd5dde4d89deb3b30ce9ca5f8b13ffe422de6a21fbc4291dd587697915d606c48a2d5dba598f8beb688cd70e99ab71939980ea7d0328c6
|
7
|
+
data.tar.gz: 15349afaf202a32b69d9ad6351298d3b95e5e53a9cf9a3641d1ab967fc07023e7d10c7d7b5c801e12caaacbdc9495d3283ddeae2dcb1963fd28d6a6ee53a4fcf
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2014 Neil E. Pearson
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,60 @@
|
|
1
|
+
# Private Gem Server
|
2
|
+
|
3
|
+
This gem provides a [Gem in a Box](https://github.com/geminabox/geminabox) implementation that pulls gems from git sources, and builds them, whenever a client requests a list of gems.
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
gem install private-gem-server
|
8
|
+
|
9
|
+
## Usage
|
10
|
+
|
11
|
+
The gem comes with the `private-gem-server` executable, which wraps the `thin` executable. It takes exactly the same command-line arguments as the [Thin web server](http://code.macournoyer.com/thin/). Run `thin -h` to see them.
|
12
|
+
|
13
|
+
sudo private-gem-server -p 80 start
|
14
|
+
|
15
|
+
## Configuration
|
16
|
+
|
17
|
+
Customize this gem using environment variables:
|
18
|
+
|
19
|
+
#### GEM_STORE
|
20
|
+
|
21
|
+
The directory in which gems (and other working files) should be stored.
|
22
|
+
|
23
|
+
#### GEM_SERVER_LOG
|
24
|
+
|
25
|
+
Path of a log file to which the server should write.
|
26
|
+
|
27
|
+
#### GEM_SOURCES
|
28
|
+
|
29
|
+
Path of a YAML file containing a list of gems to be served.
|
30
|
+
|
31
|
+
## Sources
|
32
|
+
|
33
|
+
Here's an example sources file:
|
34
|
+
|
35
|
+
```yaml
|
36
|
+
keys:
|
37
|
+
my_private_key: |
|
38
|
+
-----BEGIN RSA PRIVATE KEY-----
|
39
|
+
MIIEpAIBAAKCAQEA1Wdr4g6o62CwrNFcyVc4dQi3mrQbffhceXHD6NtnfjtrFZ7K
|
40
|
+
XKNh33X1c42D/THIS/WOULD/BE/MUCH/LONGER/IF/IT/WERE/REAL/dynzKCoLb
|
41
|
+
JwyMC+KerlXfxDSCWzE6z7bcA38dXn3hwnbokowZro40mo/NTwqY6Q==
|
42
|
+
-----END RSA PRIVATE KEY-----
|
43
|
+
|
44
|
+
gems:
|
45
|
+
"secret-component":
|
46
|
+
type: git
|
47
|
+
url: git@github.com:hx/secret-component
|
48
|
+
key: my_private_key
|
49
|
+
|
50
|
+
"very-secret-component":
|
51
|
+
type: git
|
52
|
+
url: git@github.com:hx/very-secret-component
|
53
|
+
key: my_private_key
|
54
|
+
```
|
55
|
+
|
56
|
+
For now, `git` is the only supported source. Others will come.
|
57
|
+
|
58
|
+
## Notes
|
59
|
+
|
60
|
+
- This is a premature gem with no test coverage. Don't use it.
|
@@ -0,0 +1 @@
|
|
1
|
+
require_relative 'private_gem_server'
|
@@ -0,0 +1,46 @@
|
|
1
|
+
require 'pathname'
|
2
|
+
|
3
|
+
module PrivateGemServer
|
4
|
+
module Sanity
|
5
|
+
def self.check!
|
6
|
+
|
7
|
+
# Make sure we have a valid config file
|
8
|
+
begin
|
9
|
+
config = YAML.load_file ENV['GEM_SOURCES']
|
10
|
+
rescue
|
11
|
+
fail! 'Please supply a path to your gem sources YAML file in the GEM_SOURCES environment variable.'
|
12
|
+
end
|
13
|
+
|
14
|
+
# Make sure config has a hash of gems
|
15
|
+
fail! 'Config file includes no gems' unless Hash === config && Hash === config['gems'] && !config['gems'].empty?
|
16
|
+
|
17
|
+
# Make sure we can write to our working dir
|
18
|
+
store = ENV['GEM_STORE']
|
19
|
+
fail! 'Please set GEM_STORE to a readable/writable directory' unless store &&
|
20
|
+
File.directory?(store) &&
|
21
|
+
File.readable?(store) &&
|
22
|
+
File.writable?(store) &&
|
23
|
+
File.executable?(store)
|
24
|
+
|
25
|
+
# Make sure the log is writable
|
26
|
+
log_path = ENV['GEM_SERVER_LOG']
|
27
|
+
if log_path
|
28
|
+
log_path = Pathname(log_path)
|
29
|
+
if log_path.exist?
|
30
|
+
fail! "Server log (#{log_path}) is not writable" unless log_path.writable?
|
31
|
+
else
|
32
|
+
log_path.parent.mkpath rescue fail! "Cannot create server log directory (#{log_path.parent})"
|
33
|
+
log_path.write '' rescue fail! "Cannot create server log (#{log_path})"
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
|
41
|
+
def self.fail!(reason)
|
42
|
+
STDERR << reason << "\n"
|
43
|
+
exit 1
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module PrivateGemServer
|
2
|
+
class Scanner
|
3
|
+
|
4
|
+
def initialize(app, sources_path, temp_path)
|
5
|
+
@app = app
|
6
|
+
@sources = Sources.new YAML.load_file(sources_path), temp_path
|
7
|
+
end
|
8
|
+
|
9
|
+
def call(env)
|
10
|
+
scan! if env['REQUEST_METHOD'] == 'GET' && env['PATH_INFO'] !~ %r{^/gems/}
|
11
|
+
@app.call env
|
12
|
+
end
|
13
|
+
|
14
|
+
def scan!
|
15
|
+
@sources.values.map do |source|
|
16
|
+
Thread.new { source.refresh! }.tap { |thread| thread.abort_on_exception = true }
|
17
|
+
end.each(&:join)
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,101 @@
|
|
1
|
+
require 'fileutils'
|
2
|
+
require 'shellwords'
|
3
|
+
|
4
|
+
module PrivateGemServer
|
5
|
+
class Source
|
6
|
+
class Git < self
|
7
|
+
|
8
|
+
DEBOUNCE = 10 # Minimum number of seconds between refreshes
|
9
|
+
|
10
|
+
def refresh!
|
11
|
+
return if @last_refresh && (Time.now - @last_refresh) < DEBOUNCE
|
12
|
+
prepare_git_files!
|
13
|
+
check_out_or_fetch!
|
14
|
+
find_new_versions!
|
15
|
+
@last_refresh = Time.now
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
def check_out_or_fetch!
|
21
|
+
if repo_path.join('.git').exist?
|
22
|
+
git 'git fetch', cwd: repo_path
|
23
|
+
else
|
24
|
+
repo_path.mkpath
|
25
|
+
git "git clone #{e url} #{e repo_path}"
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def key
|
30
|
+
@key ||= @sources.keys[@properties['key']]
|
31
|
+
end
|
32
|
+
|
33
|
+
def url
|
34
|
+
@url ||= @properties['url']
|
35
|
+
end
|
36
|
+
|
37
|
+
def temp_path
|
38
|
+
@temp_path ||= @sources.temp_path + 'git' + name
|
39
|
+
end
|
40
|
+
|
41
|
+
def repo_path
|
42
|
+
@repo_path = temp_path + 'repo'
|
43
|
+
end
|
44
|
+
|
45
|
+
def git(cmd, **opts)
|
46
|
+
(opts[:env] = (opts[:env] || {}).dup)['GIT_SSH'] = git_ssh_path
|
47
|
+
opts[:output] ||= logger
|
48
|
+
opts[:error] ||= logger
|
49
|
+
run!(cmd, **opts) == 0
|
50
|
+
end
|
51
|
+
|
52
|
+
def ssh_template
|
53
|
+
"#!/bin/bash\n" <<
|
54
|
+
'ssh -oStrictHostKeyChecking=no -i %s "$@"'
|
55
|
+
end
|
56
|
+
|
57
|
+
def prepare_git_files!
|
58
|
+
unless git_key_path.exist?
|
59
|
+
git_key_path.parent.mkpath
|
60
|
+
git_key_path.binwrite key
|
61
|
+
git_key_path.chmod 0400
|
62
|
+
end
|
63
|
+
unless git_ssh_path.exist?
|
64
|
+
git_ssh_path.parent.mkpath
|
65
|
+
git_ssh_path.write ssh_template % e(git_key_path)
|
66
|
+
git_ssh_path.chmod 0700
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def git_key_path
|
71
|
+
@git_key_path ||= temp_path + 'git.key'
|
72
|
+
end
|
73
|
+
|
74
|
+
def git_ssh_path
|
75
|
+
@git_ssh_path ||= temp_path + 'git_ssh'
|
76
|
+
end
|
77
|
+
|
78
|
+
def find_new_versions!
|
79
|
+
available_versions.each do |version|
|
80
|
+
build_version! version unless PrivateGemServer.has name, version[/\d.*/]
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def available_versions
|
85
|
+
list = ''
|
86
|
+
run! 'git tag -l', cwd: repo_path, output: list, error: logger
|
87
|
+
list.split("\n").select { |tag| tag =~ /^v?\d+(\.\d+)*$/ }
|
88
|
+
end
|
89
|
+
|
90
|
+
def build_version!(version)
|
91
|
+
run! "git checkout tags/#{e version} && gem build #{e name}.gemspec", cwd: repo_path, output: logger, error: logger
|
92
|
+
PrivateGemServer.add "#{repo_path}/#{name}-#{version[/\d.*/]}.gem"
|
93
|
+
end
|
94
|
+
|
95
|
+
def e(arg)
|
96
|
+
Shellwords.escape arg
|
97
|
+
end
|
98
|
+
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
require 'open3'
|
2
|
+
require 'shellwords'
|
3
|
+
|
4
|
+
require_relative 'source/git'
|
5
|
+
|
6
|
+
module PrivateGemServer
|
7
|
+
class Source
|
8
|
+
|
9
|
+
SUBCLASSES = {
|
10
|
+
git: Git
|
11
|
+
}
|
12
|
+
|
13
|
+
attr_reader :name
|
14
|
+
|
15
|
+
def self.create(name, properties, sources)
|
16
|
+
SUBCLASSES[properties['type'].to_sym].new name, properties, sources
|
17
|
+
end
|
18
|
+
|
19
|
+
def initialize(name, properties, sources)
|
20
|
+
@name = name
|
21
|
+
@properties = properties
|
22
|
+
@sources = sources
|
23
|
+
end
|
24
|
+
|
25
|
+
def refresh!
|
26
|
+
raise 'Not implemented'
|
27
|
+
end
|
28
|
+
|
29
|
+
protected
|
30
|
+
|
31
|
+
def logger
|
32
|
+
PrivateGemServer.logger
|
33
|
+
end
|
34
|
+
|
35
|
+
def run!(path, args: [], env: {}, cwd: nil, input: nil, output: nil, error: nil)
|
36
|
+
cmd = ([path] + args.map { |arg| e arg }).join ' '
|
37
|
+
opts = {}
|
38
|
+
opts[:chdir] = cwd.to_s if cwd
|
39
|
+
env = env.map { |k, v| [k.to_s, v.to_s] }.to_h
|
40
|
+
msg = env.map { |k, v| "#{e k}=#{e v}" }
|
41
|
+
msg << "cd #{e cwd};" if cwd
|
42
|
+
msg << cmd
|
43
|
+
logger.debug msg.join(' ')
|
44
|
+
Open3.popen3 env, cmd, opts do |i, o, e, t|
|
45
|
+
i << input if input; i.close
|
46
|
+
{o => output, e => error}.each do |source, target|
|
47
|
+
result = source.read
|
48
|
+
target << result if target
|
49
|
+
source.close
|
50
|
+
end
|
51
|
+
t.value.exitstatus
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def e(text)
|
56
|
+
Shellwords.escape text
|
57
|
+
end
|
58
|
+
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require_relative 'source'
|
2
|
+
require 'pathname'
|
3
|
+
|
4
|
+
module PrivateGemServer
|
5
|
+
class Sources < Hash
|
6
|
+
|
7
|
+
attr_reader :keys
|
8
|
+
attr_reader :temp_path
|
9
|
+
|
10
|
+
def initialize(config, temp_path)
|
11
|
+
@keys = config['keys'] || {}
|
12
|
+
@temp_path = Pathname(temp_path).tap(&:mkpath).realpath
|
13
|
+
merge! (config['gems'] || {}).map { |k, v| [k, Source.create(k, v, self)] }.to_h
|
14
|
+
end
|
15
|
+
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require 'logger'
|
2
|
+
|
3
|
+
require_relative 'private_gem_server/version'
|
4
|
+
require_relative 'private_gem_server/server'
|
5
|
+
require_relative 'private_gem_server/scanner'
|
6
|
+
require_relative 'private_gem_server/sources'
|
7
|
+
require_relative 'private_gem_server/sanity'
|
8
|
+
|
9
|
+
module PrivateGemServer
|
10
|
+
|
11
|
+
class << self
|
12
|
+
|
13
|
+
attr_writer :logger
|
14
|
+
|
15
|
+
def has(name, version)
|
16
|
+
gem = gems[name]
|
17
|
+
gem.include? version if gem
|
18
|
+
end
|
19
|
+
|
20
|
+
def add(file)
|
21
|
+
@gems = nil
|
22
|
+
Geminabox::GemStore.create Geminabox::IncomingGem.new File.open(file, 'rb')
|
23
|
+
end
|
24
|
+
|
25
|
+
def gems
|
26
|
+
@gems ||= Dir["#{Geminabox.data}/gems/*.gem"].group_by { |x| x[%r{(\w+(-\D\w*)*)[^/]+$}, 1] }.map { |k, v| [k, v.map { |z| z[/(\d+[\.\d+]*)\.gem$/, 1] }] }.to_h
|
27
|
+
end
|
28
|
+
|
29
|
+
def logger
|
30
|
+
@logger ||= Logger.new(STDOUT)
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
34
|
+
end
|
metadata
ADDED
@@ -0,0 +1,84 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: private-gem-server
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Neil E. Pearson
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2014-10-10 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: thin
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.6'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.6'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: geminabox
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0.1'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0.1'
|
41
|
+
description: Serve private gems for deployment in cloud environments.
|
42
|
+
email:
|
43
|
+
- neil@helium.net.au
|
44
|
+
executables: []
|
45
|
+
extensions: []
|
46
|
+
extra_rdoc_files: []
|
47
|
+
files:
|
48
|
+
- LICENSE.txt
|
49
|
+
- README.md
|
50
|
+
- lib/private-gem-server.rb
|
51
|
+
- lib/private_gem_server.rb
|
52
|
+
- lib/private_gem_server/sanity.rb
|
53
|
+
- lib/private_gem_server/scanner.rb
|
54
|
+
- lib/private_gem_server/server.rb
|
55
|
+
- lib/private_gem_server/source.rb
|
56
|
+
- lib/private_gem_server/source/git.rb
|
57
|
+
- lib/private_gem_server/sources.rb
|
58
|
+
- lib/private_gem_server/version.rb
|
59
|
+
homepage: https://github.com/hx/private-gem-server
|
60
|
+
licenses:
|
61
|
+
- MIT
|
62
|
+
metadata: {}
|
63
|
+
post_install_message:
|
64
|
+
rdoc_options: []
|
65
|
+
require_paths:
|
66
|
+
- lib
|
67
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
68
|
+
requirements:
|
69
|
+
- - ">="
|
70
|
+
- !ruby/object:Gem::Version
|
71
|
+
version: '0'
|
72
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
73
|
+
requirements:
|
74
|
+
- - ">="
|
75
|
+
- !ruby/object:Gem::Version
|
76
|
+
version: '0'
|
77
|
+
requirements: []
|
78
|
+
rubyforge_project:
|
79
|
+
rubygems_version: 2.2.2
|
80
|
+
signing_key:
|
81
|
+
specification_version: 4
|
82
|
+
summary: Private Gem Server base on Geminabox
|
83
|
+
test_files: []
|
84
|
+
has_rdoc:
|