reaper-man 0.0.1 → 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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 7dc241935585b38014666047f83d37a8a7e171d1
4
- data.tar.gz: 2e5bfd4f228ea7f741aa5c1acae67b34e9ed7c62
3
+ metadata.gz: 5e552f3927ecc938f4e80fe58a77e2ed46e3330b
4
+ data.tar.gz: 58680f7d1b1be0d7043340f423a086450ff6bfc5
5
5
  SHA512:
6
- metadata.gz: 3e27625be0b88e8a3fd9bd74586e3575ef908b7386812f19642465a5302046c23b75f147016fe5170f3653b765dabc9cfbd3df2b2b1b485d44810cd28035bb54
7
- data.tar.gz: 53bea3cc72a468f8e1ec26ca7be2f9a6a5161559aa9eb03e0192edea7f6ae7209f3c8896ec5593ab96d118f6d40b8d1844edac729e4c71674c6bdf6126db2336
6
+ metadata.gz: 8c977b039699149eba6dcbcf70be527426de5d986c9a0acc8f3cedd878c0e108d6f41e94e883b039c648c8178f13bd7e44c219c0f004f09bc2335cf6569b5875
7
+ data.tar.gz: 91b0add2951920e67f1364f8e2900e0875fed65f5b3c6b7a568fe249f497ae47469277c85ea0824486b7239ae81c60368ca6082406777088e210f1113c0ede39
data/CHANGELOG.md ADDED
@@ -0,0 +1,2 @@
1
+ # v0.1.0
2
+ * Initial release
data/CONTRIBUTING.md ADDED
@@ -0,0 +1,25 @@
1
+ # Contributing
2
+
3
+ ## Branches
4
+
5
+ ### `master` branch
6
+
7
+ The master branch is the current stable released version.
8
+
9
+ ### `develop` branch
10
+
11
+ The develop branch is the current edge of development.
12
+
13
+ ## Pull requests
14
+
15
+ * https://github.com/hw-labs/reaper-man
16
+
17
+ Please base all pull requests of the `develop` branch. Merges to
18
+ `master` only occur through the `develop` branch. Pull requests
19
+ based on `master` will likely be cherry picked.
20
+
21
+ ## Issues
22
+
23
+ Need to report an issue? Use the github issues:
24
+
25
+ * https://github.com/hw-labs/reaper-man/issues
data/LICENSE ADDED
@@ -0,0 +1,13 @@
1
+ Copyright 2015 Chris Roberts
2
+
3
+ Licensed under the Apache License, Version 2.0 (the "License");
4
+ you may not use this file except in compliance with the License.
5
+ You may obtain a copy of the License at
6
+
7
+ http://www.apache.org/licenses/LICENSE-2.0
8
+
9
+ Unless required by applicable law or agreed to in writing, software
10
+ distributed under the License is distributed on an "AS IS" BASIS,
11
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ See the License for the specific language governing permissions and
13
+ limitations under the License.
data/README.md ADDED
@@ -0,0 +1,55 @@
1
+ ## Reaper Man
2
+ #### "What can the harvest hope for, if not for the care of the Reaper Man?"
3
+ ##### - Terry Pratchett
4
+
5
+ Grow code, harvest packages
6
+
7
+ ### Harvests
8
+
9
+ Package repository is maintained via `JSON` file. Packages are added or removed
10
+ from the `JSON` registry. Repository generation will result in skeleton repository
11
+ with the proper `Release` and `Packages` files. Package paths will be not exist
12
+ within the generated repository. Resolving that issue is left to the reader.
13
+
14
+ ### Support
15
+
16
+ #### Enabled
17
+
18
+ * deb/apt
19
+ * gem/rubygems
20
+
21
+ #### In Progress
22
+
23
+ * rpm/yum
24
+
25
+ ### Usage
26
+
27
+ #### Add package to registry
28
+
29
+ ```
30
+ > reaper-man package add my_pkg.deb --packages-file registry.json
31
+ ```
32
+
33
+ ### Remove package from registry
34
+
35
+ ```
36
+ > reaper-man package remove my_pkg --packages-file registry.json
37
+ ```
38
+
39
+ or remove a specific version
40
+
41
+ ```
42
+ > reaper-man package remove my_pkg 1.0.0 --packages-file registry.json
43
+ ```
44
+
45
+ ### Create a repository
46
+
47
+ ```
48
+ > reaper-man repo create --packages-file registry.json --package-system apt --output-directory /tmp/test-repo
49
+ ```
50
+
51
+ This can also be used to update an existing repository structure.
52
+
53
+ ## Infos
54
+ * Repository: https://github.com/hw-labs/reaper-man
55
+ * IRC: Freenode @ #heavywater
data/bin/reaper-man ADDED
@@ -0,0 +1,83 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'bogo-cli'
4
+ require 'reaper-man'
5
+
6
+ Bogo::Cli::Setup.define do
7
+
8
+ on :v, :version, 'Print version' do
9
+ puts "reaper-man - Repository generation tool - [Version: #{ReaperMan::VERSION}]"
10
+ exit
11
+ end
12
+
13
+ common_options = lambda do
14
+ on :c, :config=, 'Path to configuration file'
15
+ on :k, :'signing-key=', 'Path to key for signing generated files'
16
+ on :t, :'signing-type=', 'Signing type name'
17
+ on :s, :sign, 'Enable file signing'
18
+ on :S, :'package-system=', 'Packaging system to generate repository (apt/yum/rubygems/supermarket)'
19
+ on :p, :'packages-file=', 'Path to packages file', :required => true
20
+ end
21
+
22
+ command :repo do
23
+ description 'Package repository generation'
24
+
25
+ repo_options = lambda do
26
+ on :o, :output_directory=, 'Path to output repository structure', :required => true
27
+ end
28
+
29
+ command :generate do
30
+ instance_exec(&common_options)
31
+ instance_exec(&repo_options)
32
+ description 'Generate repository'
33
+
34
+ run do |opts, args|
35
+ ReaperMan::Command::Repository::Generate.new(opts, args).execute!
36
+ end
37
+ end
38
+
39
+ end
40
+
41
+ command :package do
42
+ description 'Update packages within repositories'
43
+
44
+ package_options = lambda do
45
+ on :O, :origin=, 'Name of repository origin', :default => 'Default'
46
+ on :N, :codename=, 'Code name to add package', :default => 'all'
47
+ on :C, :component=, 'Component name to add package', :default => 'main'
48
+ end
49
+
50
+ command :add do
51
+ description 'Add package to repository manifest'
52
+ instance_exec(&common_options)
53
+ instance_exec(&package_options)
54
+
55
+ run do |opts, args|
56
+ ReaperMan::Command::Package::Add.new(opts, args).execute!
57
+ end
58
+ end
59
+
60
+ command :remove do
61
+ description 'Remove package from repository manifest'
62
+ instance_exec(&common_options)
63
+ instance_exec(&package_options)
64
+
65
+ run do |opts, args|
66
+ ReaperMan::Command::Package::Remove.new(opts, args).execute!
67
+ end
68
+ end
69
+
70
+ end
71
+
72
+ command :sign do
73
+ description 'Sign package(s)'
74
+
75
+ on :S, :'package-system=', 'Packaging system to generate repository (apt/yum/rubygems/supermarket)', :required => true
76
+ on :Z, :'signing-chunk-size=', 'Number of packages to sign at once', :default => 20, :as => Integer
77
+
78
+ run do |opts, args|
79
+ ReaperMan::Command::Sign.new(opts, args).execute!
80
+ end
81
+ end
82
+
83
+ end
@@ -0,0 +1,24 @@
1
+ require 'reaper-man'
2
+
3
+ module ReaperMan
4
+ class Command
5
+ class Package
6
+
7
+ class Add < Package
8
+
9
+ def execute!
10
+ arguments.each do |path|
11
+ run_action "Adding package to repository manifest: #{path}" do
12
+ list = ReaperMan::PackageList.new(config[:packages_file], config)
13
+ list.add_package(path)
14
+ list.write!
15
+ nil
16
+ end
17
+ end
18
+ end
19
+
20
+ end
21
+
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,24 @@
1
+ require 'reaper-man'
2
+
3
+ module ReaperMan
4
+ class Command
5
+ class Package
6
+
7
+ class Remove < Package
8
+
9
+ def execute!
10
+ arguments.each do |pkg|
11
+ run_action "Remove package from repository manifest: #{pkg}" do
12
+ list = ReaperMan::PackageList.new(config[:packages_file], config)
13
+ list.remove_package(pkg)
14
+ list.write!
15
+ nil
16
+ end
17
+ end
18
+ end
19
+
20
+ end
21
+
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,12 @@
1
+ require 'reaper-man'
2
+
3
+ module ReaperMan
4
+ class Command
5
+ class Package < Command
6
+
7
+ autoload :Add, 'reaper-man/command/package/add'
8
+ autoload :Remove, 'reaper-man/command/package/remove'
9
+
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,28 @@
1
+ require 'reaper-man'
2
+
3
+ module ReaperMan
4
+ class Command
5
+ class Repository
6
+
7
+ class Generate < Repository
8
+
9
+ def execute!
10
+ run_action 'Generating repository' do
11
+ ReaperMan::Generator.new(
12
+ config.merge(
13
+ Smash.new(
14
+ :package_config => MultiJson.load(
15
+ File.read(config[:packages_file])
16
+ ).to_smash
17
+ )
18
+ )
19
+ ).generate!
20
+ nil
21
+ end
22
+ end
23
+
24
+ end
25
+
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,11 @@
1
+ require 'reaper-man'
2
+
3
+ module ReaperMan
4
+ class Command
5
+ class Repository < Command
6
+
7
+ autoload :Generate, 'reaper-man/command/repository/generate'
8
+
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,21 @@
1
+ require 'reaper-man'
2
+
3
+ module ReaperMan
4
+ class Command
5
+ class Sign < Command
6
+
7
+ def execute!
8
+ run_action 'Signing file(s)' do
9
+ signer = Signer.new(config)
10
+ files = Dir.glob(File.join(arguments.first, '**', '*'))
11
+ files.delete_if do |path|
12
+ !File.file?(path)
13
+ end
14
+ signer.package(*files)
15
+ nil
16
+ end
17
+ end
18
+
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,11 @@
1
+ require 'reaper-man'
2
+
3
+ module ReaperMan
4
+ class Command < Bogo::Cli::Command
5
+
6
+ autoload :Repository, 'reaper-man/command/repository'
7
+ autoload :Package, 'reaper-man/command/package'
8
+ autoload :Sign, 'reaper-man/command/sign'
9
+
10
+ end
11
+ end
@@ -0,0 +1,20 @@
1
+ require 'reaper-man'
2
+
3
+ module ReaperMan
4
+ # Standard error for reaper errors
5
+ class Error < StandardError
6
+ # exit code for exception
7
+ CODE = -1
8
+
9
+ # @return [Integer]
10
+ def exit_code
11
+ self.class.const_get(:CODE)
12
+ end
13
+
14
+ # Define errors here
15
+ ['UnknownCommand', 'FileNotFound'].each_with_index do |klass_name, idx|
16
+ self.class_eval("class #{klass_name} < Error; CODE=#{idx + 1}; end;")
17
+ end
18
+
19
+ end
20
+ end
@@ -0,0 +1,117 @@
1
+ require 'reaper-man'
2
+
3
+ module ReaperMan
4
+ class Generator
5
+ # Generator methods for apt
6
+ module Apt
7
+
8
+ # Generate the repository
9
+ def generate!
10
+ generate_dists(package_config[:apt])
11
+ end
12
+
13
+ # Generate the repository dists
14
+ #
15
+ # @param pkg_hash [Hash] repository description
16
+ # @return [TrueClass]
17
+ def generate_dists(pkg_hash)
18
+ pkg_hash.each do |origin_name, dists|
19
+ dists.each do |dist_name, dist_args|
20
+ dist_args[:components].each do |component_name, arches|
21
+ arches.each do |arch_name, packages|
22
+ package_file(origin_name, dist_name, component_name, arch_name, packages)
23
+ release_headers = Smash.new
24
+ release_headers['Label'] = dist_args['label']
25
+ release_headers['Archive'] = dist_name
26
+ sign_file_if_setup do
27
+ release_file(origin_name, dist_name, component_name, arch_name, release_headers)
28
+ end
29
+ end
30
+ end
31
+ release_headers = Smash[
32
+ %w(Codename Suite Label Description Version).map do |field_name|
33
+ if(val = dist_args[field_name.downcase])
34
+ [field_name, val]
35
+ end
36
+ end.compact
37
+ ]
38
+ release_headers['Components'] = dist_args[:components].keys.join(' ')
39
+ sign_file_if_setup do
40
+ release_file(origin_name, dist_name, release_headers)
41
+ end
42
+ end
43
+ end
44
+ true
45
+ end
46
+
47
+ # Sign file if configured for signing
48
+ #
49
+ # @yield block returning file path
50
+ # @return [String] file path
51
+ def sign_file_if_setup
52
+ path = yield
53
+ if(signer && options[:sign])
54
+ signer.file(path)
55
+ end
56
+ path
57
+ end
58
+
59
+ # Create Packages file
60
+ #
61
+ # @param args [String] argument list for file path
62
+ # @return [String] path to compressed Packages file
63
+ def package_file(*args)
64
+ pkgs = args.pop
65
+ args.insert(1, 'dists')
66
+ create_file(*args.push('Packages')) do |file|
67
+ pkgs.each do |pkg_name, pkgs|
68
+ pkgs.each do |pkg_version, pkg_meta|
69
+ pkg_meta.each do |field_name, field_value|
70
+ if(field_value)
71
+ file.puts "#{field_name}: #{field_value}"
72
+ end
73
+ end
74
+ file.puts
75
+ end
76
+ end
77
+ end
78
+ compress_file(*args)
79
+ end
80
+
81
+ # Create Release file
82
+ #
83
+ # @param args [String] argument list for file path
84
+ # @return [TrueClass]
85
+ def release_file(*args)
86
+ header = args.detect{|a| a.is_a?(Hash)}
87
+ header ? args.delete(header) : header = Smash.new
88
+ header.merge(Smash[%w(Origin Codename Component Architecture).zip(args)])
89
+ header['Date'] = Time.now.utc.strftime('%a, %d %b %Y %H:%M:%S %Z')
90
+ args.insert(1, 'dists')
91
+ create_file(*args.dup.push('Release')) do |file|
92
+ contents = Dir.glob(File.join(File.dirname(file.path), '**', '*'))
93
+ header_content = header.map do |key, value|
94
+ next unless value
95
+ "#{key.to_s.capitalize}: #{value}"
96
+ end.compact.join("\n")
97
+ file.puts header_content
98
+ [['MD5Sum', :md5], ['SHA1', :sha1], ['SHA256', :sha256]].each do |field, digest|
99
+ file.puts "#{field}:"
100
+ contents.each do |content|
101
+ next if File.expand_path(content) == File.expand_path(file.path) || File.directory?(content)
102
+ File.open(content, 'r') do |content_file|
103
+ line = [' ']
104
+ line << checksum(content_file, digest)
105
+ line << content_file.size
106
+ line << content_file.path.sub(File.dirname(file.path), '').sub(/^\//, '')
107
+ file.puts line.join(' ')
108
+ end
109
+ end
110
+ end
111
+ true
112
+ end
113
+ end
114
+
115
+ end
116
+ end
117
+ end
@@ -0,0 +1,91 @@
1
+ require 'reaper-man'
2
+
3
+ module ReaperMan
4
+ class Generator
5
+ # Generator methods for rubygems
6
+ module Rubygems
7
+
8
+ # Generate the rubygems repository
9
+ #
10
+ def generate!
11
+ generate_gemstore(package_config[:rubygem])
12
+ end
13
+
14
+ def generate_gemstore(gems)
15
+ generate_indexing(gems)
16
+ write_quick_specs(gems.fetch(:release, {}))
17
+ write_quick_specs(gems.fetch(:prerelease, {}))
18
+ end
19
+
20
+ def generate_indexing(gems)
21
+ build_spec_file('specs', gems.fetch(:release, {}))
22
+ build_spec_file('latest_specs', gems.fetch(:release, {}))
23
+ build_spec_file('prerelease', gems.fetch(:prerelease, {}))
24
+ end
25
+
26
+ def create_index(gems)
27
+ [].tap do |list|
28
+ gems.each do |name, all|
29
+ all.each do |version, info|
30
+ list << [name, Gem::Version.new(version.dup), info[:platform]]
31
+ end
32
+ end
33
+ end
34
+ end
35
+
36
+ def marshal_path
37
+ ['Marshal', marshal_version].join('.')
38
+ end
39
+
40
+ def marshal_version
41
+ [Marshal::MAJOR_VERSION, Marshal::MINOR_VERSION].join('.')
42
+ end
43
+
44
+ def build_spec_file(name, gems)
45
+ index = create_index(gems)
46
+ create_file("#{name}.#{marshal_version}") do |file|
47
+ file.write(Marshal.dump(index))
48
+ end
49
+ compress_file("#{name}.#{marshal_version}")
50
+ end
51
+
52
+ def write_quick_specs(gems)
53
+ gems.each do |name, list|
54
+ list.each do |version, info|
55
+ spec = Gem::Specification.new(name)
56
+ info.each do |var, value|
57
+ if(spec.respond_to?("#{var}="))
58
+ begin
59
+ spec.send("#{var}=", value)
60
+ rescue Gem::InvalidSpecificationException => e
61
+ # TODO: Do we have a logger in this project?
62
+ end
63
+ end
64
+ end
65
+ spec.version = Gem::Version.new(info[:version])
66
+ spec.date = Time.parse(info[:date])
67
+ info[:dependencies].each do |dep|
68
+ spec.add_dependency(*dep)
69
+ end
70
+ create_file('quick', marshal_path, "#{name}-#{version}.gemspec.rz") do |file|
71
+ file.write(Marshal.dump(spec))
72
+ end
73
+ end
74
+ end
75
+ end
76
+
77
+ # Sign file if configured for signing
78
+ #
79
+ # @yield block returning file path
80
+ # @return [String] file path
81
+ def sign_file_if_setup
82
+ path = yield
83
+ if(signer && options[:sign])
84
+ signer.file(path)
85
+ end
86
+ path
87
+ end
88
+
89
+ end
90
+ end
91
+ end
@@ -0,0 +1,10 @@
1
+ require 'reaper-man'
2
+ require 'time'
3
+
4
+ module ReaperMan
5
+ class Generator
6
+ # Generator methods for yum
7
+ module Yum
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,98 @@
1
+ require 'time'
2
+ require 'zlib'
3
+ require 'fileutils'
4
+
5
+ require 'reaper-man'
6
+
7
+ module ReaperMan
8
+ # Repository generator
9
+ class Generator
10
+
11
+ autoload :Apt, 'reaper-man/generator/apt'
12
+ autoload :Rpm, 'reaper-man/generator/rpm'
13
+ autoload :Rubygems, 'reaper-man/generator/rubygems'
14
+
15
+ include Utils::Checksum
16
+
17
+ # @return [String]
18
+ attr_reader :package_system
19
+ # @return [Hash]
20
+ attr_reader :package_config
21
+ # @return [Signer, NilClass]
22
+ attr_reader :signer
23
+ # @return [Hash]
24
+ attr_reader :options
25
+
26
+ # Create new instance
27
+ #
28
+ # @param args [Hash]
29
+ # @option args [String] :package_system apt/gem/etc...
30
+ # @option args [Hash] :package_config
31
+ # @option args [Signer] :signer
32
+ def initialize(args={})
33
+ @package_system = args[:package_system]
34
+ @package_config = args.fetch(:package_config, Smash.new)
35
+ @signer = args[:signer]
36
+ @options = args
37
+ extend self.class.const_get(package_system.to_s.split('_').map(&:capitalize).join.to_sym)
38
+ end
39
+
40
+ # Generate new repository
41
+ def generate!
42
+ raise NoMethodError.new 'Not implemented'
43
+ end
44
+
45
+ # Create new file
46
+ #
47
+ # @param name [String] argument list joined to output directory
48
+ # @yield block executed with file
49
+ # @yieldparam [String] path to file
50
+ # @return [String] path to file
51
+ def create_file(*name)
52
+ path = File.join(options[:output_directory], *name)
53
+ FileUtils.mkdir_p(File.dirname(path))
54
+ file = File.open(path, 'wb+')
55
+ if(block_given?)
56
+ yield file
57
+ end
58
+ file.close unless file.closed?
59
+ path
60
+ end
61
+
62
+ # Updates a file
63
+ #
64
+ # @param name [String] argument list joined to output directory
65
+ # @yield block executed with file
66
+ # @yieldparam [String] path to file
67
+ # @return [String] path to file
68
+ def for_file(*name)
69
+ path = File.join(options[:output_directory], *name)
70
+ FileUtils.mkdir_p(File.dirname(path))
71
+ if(block_given?)
72
+ file = File.open(path, 'a+')
73
+ yield file
74
+ file.close
75
+ end
76
+ path
77
+ end
78
+
79
+ # Compress a file (gzip)
80
+ #
81
+ # @param name [String] argument list joined to output directory
82
+ # @return [String] path to compressed file
83
+ def compress_file(*path)
84
+ compressed_path = path.dup
85
+ compressed_path.push("#{compressed_path.pop}.gz")
86
+ base_file = File.open(for_file(path))
87
+ create_file(compressed_path) do |file|
88
+ compressor = Zlib::GzipWriter.new(file)
89
+ while(data = base_file.read(2048))
90
+ compressor.write(data)
91
+ end
92
+ compressor.close
93
+ end
94
+ end
95
+
96
+ end
97
+
98
+ end