private-gem-server 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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,7 @@
1
+ require 'geminabox'
2
+
3
+ module PrivateGemServer
4
+ class Server < Geminabox::Server
5
+
6
+ end
7
+ 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,3 @@
1
+ module PrivateGemServer
2
+ VERSION = '0.1.0'
3
+ 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: