reaper-man 0.0.1 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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