melai 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +21 -0
- data/Gemfile +4 -0
- data/LICENSE +22 -0
- data/README.md +67 -0
- data/Rakefile +44 -0
- data/bin/melai +108 -0
- data/features/directories.feature +26 -0
- data/features/repository.feature +45 -0
- data/features/step_definitions/directories_steps.rb +5 -0
- data/features/support/env.rb +15 -0
- data/lib/melai.rb +80 -0
- data/lib/melai/dir_helpers.rb +43 -0
- data/lib/melai/package_helpers.rb +168 -0
- data/lib/melai/string_helpers.rb +40 -0
- data/lib/melai/version.rb +3 -0
- data/melai.gemspec +38 -0
- data/melai.rdoc +4 -0
- data/templates/apt-ftparchive.conf.erb +26 -0
- data/templates/debian.list.erb +2 -0
- data/templates/redhat.repo.erb +6 -0
- metadata +198 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2012 Mike Fiedler
|
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,67 @@
|
|
1
|
+
# Melai
|
2
|
+
|
3
|
+
Melai is a command-line tool to create software package repositories for
|
4
|
+
APT and YUM package management tools.
|
5
|
+
|
6
|
+
## Installation
|
7
|
+
This tool is a standalone tool, and should be installed wither via RubyGems:
|
8
|
+
|
9
|
+
$ gem install melai
|
10
|
+
|
11
|
+
If you'd rather install dependencies and install from source:
|
12
|
+
|
13
|
+
$ bundle install && rake install
|
14
|
+
|
15
|
+
## Usage
|
16
|
+
|
17
|
+
### Requirements
|
18
|
+
Some prerequisites are needed, and exist currently on Debian/Ubuntu based distros.
|
19
|
+
|
20
|
+
* `apt-ftparchive` (Debian-style packages) - `sudo apt-get install apt-utils`
|
21
|
+
* `createrepo` (RedHat-style packages) - `sudo apt-get install createrepo`
|
22
|
+
|
23
|
+
Both of these external tools are needed to build the required repo metadata.
|
24
|
+
|
25
|
+
NOTE: This process cannot be run on RedHat/CentOS yet - as they do not have the
|
26
|
+
tools to work with Debian-style packages.
|
27
|
+
|
28
|
+
## Execution
|
29
|
+
|
30
|
+
An example:
|
31
|
+
|
32
|
+
melai -r newrepo create -p sourcepackages -u "http://mypackageserver.com/myrepo"
|
33
|
+
|
34
|
+
Another way is to initialize a config, and use that:
|
35
|
+
|
36
|
+
melai -r newrepo initconfig
|
37
|
+
|
38
|
+
This will create a file named `~/.melai.rc`. Edit it to contain your values:
|
39
|
+
|
40
|
+
---
|
41
|
+
:help: false
|
42
|
+
:r: newrepo
|
43
|
+
:repos-path: newrepo
|
44
|
+
commands:
|
45
|
+
:create:
|
46
|
+
:pkgs-path: sourcepackages
|
47
|
+
:url-root: "http://mypackageserver.com/myrepo"
|
48
|
+
:list: {}
|
49
|
+
:destroy: {}
|
50
|
+
:p: tmpfoo
|
51
|
+
:pkgs-path: tmpfoo
|
52
|
+
:u: "http://mypackageserver.com/myrepo"
|
53
|
+
:url-root: "http://mypackageserver.com/myrepo"
|
54
|
+
:list: {}
|
55
|
+
:destroy: {}
|
56
|
+
|
57
|
+
Then you can run `melai create` with no arguments.
|
58
|
+
See [here](https://github.com/davetron5000/gli/wiki/Config) for more details.
|
59
|
+
|
60
|
+
## Contributing
|
61
|
+
|
62
|
+
1. Fork it
|
63
|
+
1. Create your feature branch (`git checkout -b my-new-feature`)
|
64
|
+
1. Test your changes (`rake test`)
|
65
|
+
1. Commit your changes (`git commit -am 'Added some feature'`)
|
66
|
+
1. Push to the branch (`git push origin my-new-feature`)
|
67
|
+
1. Create new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1,44 @@
|
|
1
|
+
#!/usr/bin/env rake
|
2
|
+
require 'bundler/gem_tasks'
|
3
|
+
require 'cucumber'
|
4
|
+
require 'cucumber/rake/task'
|
5
|
+
|
6
|
+
task :default => [:install, :test]
|
7
|
+
|
8
|
+
desc "Run tests"
|
9
|
+
task :test => [:features, :tailor]
|
10
|
+
|
11
|
+
Cucumber::Rake::Task.new(:features) do |t|
|
12
|
+
# t.cucumber_opts = ['--format pretty -x']
|
13
|
+
|
14
|
+
t.cucumber_opts = ['--format', 'progress', '-x']
|
15
|
+
t.cucumber_opts += ['features']
|
16
|
+
|
17
|
+
# This turns on the GLI debugging backtraces
|
18
|
+
t.cucumber_opts += ['GLI_DEBUG=true']
|
19
|
+
end
|
20
|
+
|
21
|
+
# https://github.com/turboladen/tailor
|
22
|
+
require 'tailor/rake_task'
|
23
|
+
Tailor::RakeTask.new do |task|
|
24
|
+
task.file_set('lib/**/*.rb', 'code') do |style|
|
25
|
+
style.max_line_length 100, level: :warn
|
26
|
+
style.max_code_lines_in_method 50, level: :warn
|
27
|
+
end
|
28
|
+
task.file_set('bin/*', 'binaries') do |style|
|
29
|
+
style.max_line_length 100, level: :warn
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
require 'rdoc/task'
|
34
|
+
Rake::RDocTask.new do |rd|
|
35
|
+
rd.main = "README.md"
|
36
|
+
rd.rdoc_files.include("README.md","lib/**/*.rb","bin/**/*")
|
37
|
+
rd.title = 'melai'
|
38
|
+
end
|
39
|
+
|
40
|
+
# File lib/tasks/notes.rake
|
41
|
+
desc "Find notes in code"
|
42
|
+
task :notes do
|
43
|
+
puts `grep --exclude=Rakefile -r 'OPTIMIZE:\\|FIXME:\\|TODO:' .`
|
44
|
+
end
|
data/bin/melai
ADDED
@@ -0,0 +1,108 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# $: << File.expand_path(File.dirname(File.realpath(__FILE__)) + '/../lib')
|
3
|
+
# require 'rubygems'
|
4
|
+
|
5
|
+
require 'gli'
|
6
|
+
require_relative '../lib/melai'
|
7
|
+
|
8
|
+
include GLI
|
9
|
+
|
10
|
+
program_desc "Describe your application here"
|
11
|
+
|
12
|
+
version Melai::VERSION
|
13
|
+
|
14
|
+
# desc "Describe some switch here"
|
15
|
+
# switch [:s, :switch]
|
16
|
+
|
17
|
+
desc "Root directory for repositories, relative to current directory"
|
18
|
+
default_value "repo"
|
19
|
+
arg_name "REPOS_PATH"
|
20
|
+
flag [:r, "repos-path"]
|
21
|
+
|
22
|
+
# https://github.com/davetron5000/gli/wiki/Config
|
23
|
+
config_file '.melai.rc'
|
24
|
+
|
25
|
+
desc "Create a repository structure"
|
26
|
+
command :create do |c|
|
27
|
+
# TODO: add a force option to rebuild from scratch
|
28
|
+
|
29
|
+
c.desc "Source directory containing all packages, relative to current directory"
|
30
|
+
c.default_value "srcpkgs"
|
31
|
+
c.arg_name "PKGS_PATH"
|
32
|
+
c.flag [:p, "pkgs-path"]
|
33
|
+
|
34
|
+
c.desc "FQDN of the web server, must include http:// and desired docroot"
|
35
|
+
c.default_value "http://somewhere.com/repo"
|
36
|
+
c.arg_name "URL_ROOT"
|
37
|
+
c.flag [:u, "url-root"]
|
38
|
+
|
39
|
+
c.action do |global_options, options, args|
|
40
|
+
repositories_path = File.absolute_path(global_options[:r])
|
41
|
+
packages_path = File.absolute_path(options[:p])
|
42
|
+
url_root = options[:u]
|
43
|
+
$melai.create(repositories_path, packages_path, url_root)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
desc "List all packages in a directory"
|
48
|
+
command :list do |c|
|
49
|
+
|
50
|
+
c.desc "Source directory containing all packages"
|
51
|
+
c.default_value "srcpkgs"
|
52
|
+
c.arg_name "PKGS_PATH"
|
53
|
+
c.flag [:p, "pkgs-path"]
|
54
|
+
|
55
|
+
c.action do |global_options, options, args|
|
56
|
+
puts $melai.list(options[:p])
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
desc "Destroy the repository directory completely"
|
61
|
+
command :destroy do |c|
|
62
|
+
c.action do |global_options, options, args|
|
63
|
+
# TODO: Add a --force option
|
64
|
+
# TODO: Add a "Are you sure? (y/n)" prompt
|
65
|
+
$melai.destroy(global_options[:r])
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
|
70
|
+
pre do |global, command, options, args|
|
71
|
+
# Pre logic here
|
72
|
+
# Return true to proceed; false to abort and not call the
|
73
|
+
# chosen command
|
74
|
+
# Use skips_pre before a command to skip this block
|
75
|
+
# on that command only
|
76
|
+
# true
|
77
|
+
$melai = Melai::CommandHandler.new
|
78
|
+
# TODO: Why do I have to extend the CommandHandler class? Inclusion?
|
79
|
+
$melai.extend(Melai::DirHelpers)
|
80
|
+
$melai.extend(Melai::PackageHelpers)
|
81
|
+
$melai.extend(Melai::StringHelpers)
|
82
|
+
end
|
83
|
+
|
84
|
+
post do |global, command, options, args|
|
85
|
+
# Post logic here
|
86
|
+
# Use skips_post before a command to skip this
|
87
|
+
# block on that command only
|
88
|
+
end
|
89
|
+
|
90
|
+
on_error do |exception|
|
91
|
+
# Error logic here
|
92
|
+
# return false to skip default error handling
|
93
|
+
true
|
94
|
+
end
|
95
|
+
|
96
|
+
exit GLI.run(ARGV)
|
97
|
+
|
98
|
+
|
99
|
+
# module Melai
|
100
|
+
# cmd_line = CommandLine.new(ARGV)
|
101
|
+
# # review, status = Linter.check(cmd_line)
|
102
|
+
# # printer = cmd_line.show_context? ? ContextOutput.new : SummaryOutput.new
|
103
|
+
# # printer.output(review)
|
104
|
+
# exit status.to_i
|
105
|
+
# end
|
106
|
+
#
|
107
|
+
#
|
108
|
+
#
|
@@ -0,0 +1,26 @@
|
|
1
|
+
Feature: Set up the repository directory
|
2
|
+
In order to increase the amount of confidence in repeatable package repositories,
|
3
|
+
As an operator
|
4
|
+
I want to set up all the directories I need
|
5
|
+
|
6
|
+
Scenario: Listing an empty source directory returns no files
|
7
|
+
Given a directory named "emptysrcpkgs"
|
8
|
+
When I successfully run `melai list -p emptysrcpkgs`
|
9
|
+
Then the output should contain exactly ""
|
10
|
+
|
11
|
+
Scenario: List all package files in a source directory
|
12
|
+
Given a directory named "srcpkgs"
|
13
|
+
And an empty file named "srcpkgs/example-1.0.deb"
|
14
|
+
And an empty file named "srcpkgs/example-1.0.rpm"
|
15
|
+
When I successfully run `melai list -p srcpkgs`
|
16
|
+
Then the output should contain:
|
17
|
+
"""
|
18
|
+
srcpkgs/example-1.0.deb
|
19
|
+
srcpkgs/example-1.0.rpm
|
20
|
+
"""
|
21
|
+
|
22
|
+
# Final step, cleans up
|
23
|
+
Scenario: A repository directory exists and I want to destroy it
|
24
|
+
Given a directory named "repo"
|
25
|
+
When I successfully run `melai -r repo destroy`
|
26
|
+
Then a directory named "repo" should not exist
|
@@ -0,0 +1,45 @@
|
|
1
|
+
Feature: Create repositories from files
|
2
|
+
In order provide packages to end users,
|
3
|
+
As an operator
|
4
|
+
I want to create multiple repositories from a set of files
|
5
|
+
|
6
|
+
Background:
|
7
|
+
Given I double `apt-ftparchive` with exit status 0
|
8
|
+
And I double `createrepo` with exit status 0
|
9
|
+
|
10
|
+
Scenario: Create a repository for source packages in a semi-flat directory
|
11
|
+
Given an empty file named "srcpkgs/foo-1.0.0.i686.rpm"
|
12
|
+
And an empty file named "srcpkgs/foo-1.0.0.i386.deb"
|
13
|
+
And an empty file named "srcpkgs/ubuntu/foo-1.0.0-bar.i386.deb"
|
14
|
+
When I successfully run `melai -r repo create -p srcpkgs`
|
15
|
+
Then a directory named "repo/redhat/1.0/i686/RPMS" should exist
|
16
|
+
And a directory named "repo/redhat/os/i686/RPMS" should exist
|
17
|
+
And a directory named "repo/debian-sysvinit/dists/dist/10gen/binary-i386" should exist
|
18
|
+
And a directory named "repo/ubuntu-upstart/dists/dist/10gen/binary-i386" should exist
|
19
|
+
|
20
|
+
Scenario Outline: Create a repository directory structure for a given file
|
21
|
+
Given a directory named "repo" does not exist
|
22
|
+
And an empty file named <SrcFile>
|
23
|
+
When I successfully run `melai -r repo create -p srcpkgs`
|
24
|
+
Then a directory named <RepoDir> should exist
|
25
|
+
Examples:
|
26
|
+
| SrcFile | RepoDir |
|
27
|
+
| "srcpkgs/redhat/foo-2.0.0.i686.rpm" | "repo/redhat/os/i686/RPMS" |
|
28
|
+
| "srcpkgs/redhat/foo-2.0.1.i686.rpm" | "repo/redhat/2.0/i686/RPMS" |
|
29
|
+
| "srcpkgs/redhat/foo-2.0.2.x86_64.rpm" | "repo/redhat/2.0/x86_64/RPMS" |
|
30
|
+
| "srcpkgs/debian/foo-2.0.0.i386.deb" | "repo/debian-sysvinit/dists/dist/10gen/binary-i386/" |
|
31
|
+
| "srcpkgs/redhat/os/i686/RPMS/bar-server-2.0.6-version_1.i686.rpm" | "repo/redhat/os/i686/RPMS/" |
|
32
|
+
| "srcpkgs/redhat/os/i686/RPMS/bar-unstable-2.1.0-version_1.i686.rpm" | "repo/redhat/2.1/i686/RPMS/" |
|
33
|
+
| "srcpkgs/redhat/os/i686/RPMS/bar18-server-1.8.5-version_1.i686.rpm" | "repo/redhat/1.8/i686/RPMS/" |
|
34
|
+
| "srcpkgs/redhat/os/i686/RPMS/bar-2.0.6-version_1.i686.rpm" | "repo/redhat/2.0/i686/RPMS/" |
|
35
|
+
|
36
|
+
Scenario Outline: Create a repository file
|
37
|
+
Given a directory named "repo" does not exist
|
38
|
+
And an empty file named <SrcFile>
|
39
|
+
When I successfully run `melai -r repo create -p srcpkgs`
|
40
|
+
Then a file named <RepoTemplate> should exist
|
41
|
+
Examples:
|
42
|
+
| SrcFile | RepoTemplate |
|
43
|
+
| "srcpkgs/redhat/foo-2.0.0.i686.rpm" | "repo/redhat/os/i686/RPMS/10gen.repo" |
|
44
|
+
| "srcpkgs/debian/foo-2.0.0.i386.deb" | "repo/debian-sysvinit/dists/dist/10gen/binary-i386/10gen.list" |
|
45
|
+
| "srcpkgs/ubuntu/foo-2.0.0.i386.deb" | "repo/ubuntu-upstart/dists/dist/10gen/binary-i386/10gen.list" |
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# Set up the environment for testing
|
2
|
+
require 'aruba/cucumber'
|
3
|
+
require 'aruba-doubles/cucumber'
|
4
|
+
|
5
|
+
Before do
|
6
|
+
@aruba_timeout_seconds = 5
|
7
|
+
# @dirs = ["tmp/aruba"]
|
8
|
+
end
|
9
|
+
|
10
|
+
After do |s|
|
11
|
+
# Tell Cucumber to quit after this scenario is done - if it failed.
|
12
|
+
# This is useful to inspect the 'tmp/aruba' directory before any other
|
13
|
+
# steps are executed and clear it out.
|
14
|
+
Cucumber.wants_to_quit = true if s.failed?
|
15
|
+
end
|
data/lib/melai.rb
ADDED
@@ -0,0 +1,80 @@
|
|
1
|
+
# require 'rubygems'
|
2
|
+
require_relative 'melai/version'
|
3
|
+
require_relative 'melai/dir_helpers'
|
4
|
+
require_relative 'melai/package_helpers'
|
5
|
+
require_relative 'melai/string_helpers'
|
6
|
+
|
7
|
+
module Melai
|
8
|
+
#
|
9
|
+
# This module is the main module. Creates a class of CommandHandler.
|
10
|
+
# Not entirely sure if the code is structured correctly. Might have to
|
11
|
+
# rethink module vs class locations, based on inheritance.
|
12
|
+
#
|
13
|
+
class CommandHandler
|
14
|
+
|
15
|
+
# Creates/updates a symlink-based repository structure
|
16
|
+
#
|
17
|
+
# @param [String] a directory to build into
|
18
|
+
# @param [String] a directory containing the source packages
|
19
|
+
def create(repositories_path, packages_path, url_root)
|
20
|
+
puts "Creating a repository at #{repositories_path}."
|
21
|
+
ensure_directory(repositories_path)
|
22
|
+
|
23
|
+
# Accumulate a Hash keyed by repository filesystem path
|
24
|
+
# whose values are hashes of {:needs_update => bool,
|
25
|
+
# :arch => String, :variant => String }
|
26
|
+
repositories = Hash.new
|
27
|
+
|
28
|
+
find_packages(packages_path).each do |package_path|
|
29
|
+
package_metadata(package_path, repositories_path).each do |metadata|
|
30
|
+
repository_path = metadata[:repository_path]
|
31
|
+
needs_update = ensure_symlink(metadata[:symlink_path], metadata[:package_path])
|
32
|
+
|
33
|
+
if repositories.include?(repository_path)
|
34
|
+
repositories[repository_path][:needs_update] ||= needs_update
|
35
|
+
else
|
36
|
+
repositories[repository_path] = {
|
37
|
+
:needs_update => needs_update,
|
38
|
+
:variant => metadata[:variant],
|
39
|
+
:arch => metadata[:arch],
|
40
|
+
:repository_path => metadata[:repository_path],
|
41
|
+
:repository_prefix => metadata[:repository_prefix]
|
42
|
+
}
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
# Now we have a Hash of all the repo directories that need updating by a value of 'true'
|
48
|
+
repositories.each do |repository_path, metadata|
|
49
|
+
next unless metadata[:needs_update]
|
50
|
+
|
51
|
+
repo_template(metadata, repositories_path, url_root)
|
52
|
+
update_repo_metadata(metadata, repositories_path, packages_path)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
# List all package files found
|
57
|
+
#
|
58
|
+
# @param [String] a directory containing the source packages
|
59
|
+
# @return [Array] an array of filenames
|
60
|
+
def list(packages_path)
|
61
|
+
find_packages(packages_path)
|
62
|
+
end
|
63
|
+
|
64
|
+
# Destroy the repository directory completely.
|
65
|
+
# This is a glorified `rm -fr repo/` command, with a simple guard.
|
66
|
+
#
|
67
|
+
# @param [String] the root of repository directory
|
68
|
+
def destroy(repositories_path)
|
69
|
+
if repositories_path == "/"
|
70
|
+
exit_now!("WTF are you trying to do?", 1)
|
71
|
+
elsif Dir.exists?(repositories_path)
|
72
|
+
puts "Removing the entire #{repositories_path}"
|
73
|
+
FileUtils.remove_dir repositories_path
|
74
|
+
puts "It's gone!"
|
75
|
+
else
|
76
|
+
puts "Nothing there... idiot."
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
module Melai
|
2
|
+
|
3
|
+
# This module provides some helper methods, specificlally pertaining
|
4
|
+
# to directory creation, removal, searching, et al.
|
5
|
+
module DirHelpers
|
6
|
+
|
7
|
+
# Get any files with a known package extension
|
8
|
+
#
|
9
|
+
# @param [String] a directory to evaluate
|
10
|
+
# @return [Array] an array of filenmes
|
11
|
+
def find_packages(packages_path)
|
12
|
+
packages = File.join(packages_path, "**", "*.{rpm,deb}")
|
13
|
+
return Dir.glob(packages).sort()
|
14
|
+
end
|
15
|
+
|
16
|
+
# Ensure a directory exists
|
17
|
+
#
|
18
|
+
# @param [String] a directory to evaluate
|
19
|
+
# @return [Bool] true if the directory was created, false if already exists
|
20
|
+
def ensure_directory(directory)
|
21
|
+
unless File.directory?(directory)
|
22
|
+
FileUtils.mkdir_p(directory)
|
23
|
+
return true
|
24
|
+
end
|
25
|
+
return false
|
26
|
+
end
|
27
|
+
|
28
|
+
# Ensure a symlink exists
|
29
|
+
#
|
30
|
+
# @param [String] symlink_name: /foo/bar/i-am-a-symlink.rpm
|
31
|
+
# @param [String] original_file: /baz/i-am-a-real-file.rpm
|
32
|
+
# @return [Bool] true if the link was created, false if already exists
|
33
|
+
def ensure_symlink(symlink_name, original_file)
|
34
|
+
unless File.symlink?(symlink_name)
|
35
|
+
ensure_directory(File.dirname(symlink_name))
|
36
|
+
target = File.absolute_path(original_file)
|
37
|
+
File.symlink(target, symlink_name)
|
38
|
+
return true
|
39
|
+
end
|
40
|
+
return false
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,168 @@
|
|
1
|
+
require 'erb'
|
2
|
+
require 'tempfile'
|
3
|
+
require 'mixlib/shellout'
|
4
|
+
require_relative 'dir_helpers'
|
5
|
+
require_relative 'string_helpers'
|
6
|
+
|
7
|
+
module Melai
|
8
|
+
|
9
|
+
# This module provides some package helper methods
|
10
|
+
module PackageHelpers
|
11
|
+
|
12
|
+
# Return an array of package metadata hashes. Each
|
13
|
+
# array element describes a particular repository
|
14
|
+
# that this package should be a member of.
|
15
|
+
def package_metadata(package_path, repositories_path)
|
16
|
+
case File.extname(package_path)
|
17
|
+
when '.rpm'
|
18
|
+
return rpm_package_metadata(package_path, repositories_path)
|
19
|
+
when '.deb'
|
20
|
+
if File.fnmatch('*ubuntu*', package_path)
|
21
|
+
return ubuntu_package_metadata(package_path, repositories_path)
|
22
|
+
else File.fnmatch('*debian*', package_path)
|
23
|
+
return debian_package_metadata(package_path, repositories_path)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def repo_template(metadata, repositories_path, url_root)
|
29
|
+
repository_path = metadata[:repository_path]
|
30
|
+
case repository_path
|
31
|
+
when /redhat/
|
32
|
+
source = "redhat.repo.erb"
|
33
|
+
target = "10gen.repo"
|
34
|
+
url_path = File.join(metadata[:repository_prefix], metadata[:variant], metadata[:arch])
|
35
|
+
else
|
36
|
+
source = "debian.list.erb"
|
37
|
+
target = "10gen.list"
|
38
|
+
url_path = File.join(metadata[:repository_prefix])
|
39
|
+
end
|
40
|
+
|
41
|
+
here = File.dirname(__FILE__)
|
42
|
+
template = ERB.new(File.read(File.join(here, "..", "..", "templates", source)))
|
43
|
+
output = File.new(File.join(repository_path, target), "w")
|
44
|
+
output.write(template.result(binding))
|
45
|
+
end
|
46
|
+
|
47
|
+
def update_repo_metadata(metadata, repositories_path, packages_path)
|
48
|
+
repository_path = metadata[:repository_path]
|
49
|
+
|
50
|
+
# e.g /data/packages/cache/debian-sysvinit/dists/
|
51
|
+
cache_path = File.join(packages_path, "cache", metadata[:repository_prefix])
|
52
|
+
ensure_directory(cache_path)
|
53
|
+
|
54
|
+
case repository_path
|
55
|
+
when /redhat/
|
56
|
+
begin
|
57
|
+
# e.g. repo/redhat/2.0/x86_64/
|
58
|
+
variant_dir = File.join(repositories_path,
|
59
|
+
metadata[:repository_prefix],
|
60
|
+
metadata[:variant],
|
61
|
+
metadata[:arch])
|
62
|
+
|
63
|
+
shell_out("createrepo --pretty -c #{cache_path} #{variant_dir}")
|
64
|
+
rescue Exception=>e
|
65
|
+
exit_now!("Could not complete createrepo:\n#{e}", 1)
|
66
|
+
end
|
67
|
+
|
68
|
+
else
|
69
|
+
# Do Debian-style repo builds
|
70
|
+
here = File.dirname(__FILE__)
|
71
|
+
fileloc = File.join(here, "../..", "templates", "apt-ftparchive.conf.erb")
|
72
|
+
template = ERB.new(File.read(fileloc))
|
73
|
+
output = Tempfile.new("apt-ftparchive.conf")
|
74
|
+
output.write(template.result(binding))
|
75
|
+
output.close()
|
76
|
+
|
77
|
+
begin
|
78
|
+
# Generates the Packages, Contents files
|
79
|
+
shell_out("apt-ftparchive generate #{output.path}")
|
80
|
+
|
81
|
+
# Generate the Release files on stdout
|
82
|
+
variant_dir = File.join(repositories_path,
|
83
|
+
metadata[:repository_prefix],
|
84
|
+
"dists",
|
85
|
+
metadata[:variant])
|
86
|
+
|
87
|
+
result = shell_out("apt-ftparchive -c #{output.path} release #{variant_dir}")
|
88
|
+
|
89
|
+
# Dump the Release stdout to a Release file
|
90
|
+
release_file = File.join(variant_dir, "Release.new")
|
91
|
+
File.open(release_file, "w").write(result.stdout)
|
92
|
+
# This ensures that the Release file is not included within itself
|
93
|
+
File.rename(release_file, File.join(variant_dir, "Release"))
|
94
|
+
|
95
|
+
# TODO: GPG Sign the Release file
|
96
|
+
|
97
|
+
rescue Exception=>e
|
98
|
+
exit_now!("Could not complete apt-ftparchive:\n#{e}", 1)
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
private
|
104
|
+
|
105
|
+
def shell_out(command)
|
106
|
+
proc = Mixlib::ShellOut.new(command)
|
107
|
+
proc.run_command
|
108
|
+
proc.error!
|
109
|
+
return proc
|
110
|
+
end
|
111
|
+
|
112
|
+
def rpm_package_metadata(package_path, repositories_path)
|
113
|
+
generate_metadata(package_path, repositories_path, ["os"]) do |variant, arch|
|
114
|
+
# Return path segments, relative to repositories_path,
|
115
|
+
# necessary to construct a path to the correct directory
|
116
|
+
# for RPM packages in the given variant and arch.
|
117
|
+
["redhat", variant, arch, "RPMS"]
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
def debian_package_metadata(package_path, repositories_path)
|
122
|
+
generate_metadata(package_path, repositories_path, ["dist"]) do |variant, arch|
|
123
|
+
# Return path segments, relative to repositories_path,
|
124
|
+
# necessary to construct a path to the correct directory
|
125
|
+
# for Debian packages in the given variant and arch.
|
126
|
+
["debian-sysvinit", "dists", variant, "10gen", "binary-#{arch}"]
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
def ubuntu_package_metadata(package_path, repositories_path)
|
131
|
+
generate_metadata(package_path, repositories_path, ["dist"]) do |variant, arch|
|
132
|
+
# Return path segments, relative to repositories_path,
|
133
|
+
# necessary to construct a path to the correct directory
|
134
|
+
# for Ubuntu packages in the given variant and arch.
|
135
|
+
["ubuntu-upstart", "dists", variant, "10gen", "binary-#{arch}"]
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
def generate_metadata(package_path, repositories_path, variants)
|
140
|
+
# Since we want to provide both a 'base' variant and a version-specific
|
141
|
+
# one, we build an array of variants based on the the version maj.min
|
142
|
+
version, dist = get_version_from_filename(package_path)
|
143
|
+
arch = get_arch_from_filename(package_path)
|
144
|
+
|
145
|
+
variants << dist
|
146
|
+
|
147
|
+
package_metadata = []
|
148
|
+
variants.each do |variant|
|
149
|
+
# This is where the link will end up
|
150
|
+
path_segments = yield variant, arch
|
151
|
+
repository_path = File.join(repositories_path, *path_segments)
|
152
|
+
repository_prefix = path_segments[0]
|
153
|
+
|
154
|
+
# Create a symlink from the source file to the destination directories
|
155
|
+
package_metadata << {
|
156
|
+
:symlink_path => File.join(repository_path, File.basename(package_path)),
|
157
|
+
:package_path => package_path,
|
158
|
+
:variant => variant,
|
159
|
+
:arch => arch,
|
160
|
+
:repository_path => repository_path,
|
161
|
+
:repository_prefix => repository_prefix
|
162
|
+
}
|
163
|
+
end
|
164
|
+
|
165
|
+
return package_metadata
|
166
|
+
end
|
167
|
+
end
|
168
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
require 'versionomy'
|
2
|
+
|
3
|
+
module Melai
|
4
|
+
|
5
|
+
# This module provides some string helper methods
|
6
|
+
module StringHelpers
|
7
|
+
|
8
|
+
# Find the arch name within a filename
|
9
|
+
#
|
10
|
+
# @params [String] A filename
|
11
|
+
# @return [String] the arch name
|
12
|
+
def get_arch_from_filename(filename)
|
13
|
+
case
|
14
|
+
when matched = filename.match(/(i686|x86_64|i386|amd64)/)
|
15
|
+
arch = matched[0]
|
16
|
+
else
|
17
|
+
return 'unknown'
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
# Find the version within a filename
|
22
|
+
#
|
23
|
+
# @params [String] A filename
|
24
|
+
# @return [Object] the version object, Versionomy-style
|
25
|
+
# @return [String] The Major + Minor versions
|
26
|
+
def get_version_from_filename(filename)
|
27
|
+
version = Versionomy.parse(/(\d+)\.(\d+)\.(\d+)/.match(filename).to_s)
|
28
|
+
# TODO: If we start packaging prerelease/rc packages, try:
|
29
|
+
# (\d+)\.(\d+)\.(\d+)(?:-[^\.]+)?
|
30
|
+
dist = [version.major, version.minor].join(".")
|
31
|
+
return version, dist
|
32
|
+
end
|
33
|
+
|
34
|
+
# TODO: needs work, currently unused
|
35
|
+
def get_unstable_from_filename(filename)
|
36
|
+
unstable = /unstable/.match(filename)
|
37
|
+
return unstable
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
data/melai.gemspec
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
require File.expand_path('../lib/melai/version', __FILE__)
|
3
|
+
|
4
|
+
Gem::Specification.new do |gem|
|
5
|
+
|
6
|
+
gem.name = 'melai'
|
7
|
+
gem.summary = %q{melai builds multi-platform repositories for a given list of package files}
|
8
|
+
gem.description = %q{Build your repositories with melai}
|
9
|
+
gem.version = Melai::VERSION
|
10
|
+
|
11
|
+
gem.files = `git ls-files`.split($\)
|
12
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
13
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
14
|
+
gem.require_paths = ['lib']
|
15
|
+
|
16
|
+
|
17
|
+
# GLI (Github-Like Interface) http://davetron5000.github.com/gli/
|
18
|
+
gem.add_dependency 'gli'
|
19
|
+
|
20
|
+
# versiononmy http://dazuma.github.com/versionomy/
|
21
|
+
gem.add_dependency 'versionomy'
|
22
|
+
|
23
|
+
# MixLib::ShellOut to perform system-level commands
|
24
|
+
gem.add_dependency 'mixlib-shellout'
|
25
|
+
|
26
|
+
gem.add_development_dependency 'rake'
|
27
|
+
gem.add_development_dependency 'aruba'
|
28
|
+
gem.add_development_dependency 'aruba-doubles'
|
29
|
+
gem.add_development_dependency 'pry'
|
30
|
+
|
31
|
+
# tailor, for style. https://github.com/turboladen/tailor
|
32
|
+
gem.add_development_dependency 'tailor'
|
33
|
+
|
34
|
+
gem.authors = ["Mike Fiedler"]
|
35
|
+
gem.email = ["miketheman@gmail.com"]
|
36
|
+
gem.homepage = "http://www.miketheman.net"
|
37
|
+
|
38
|
+
end
|
data/melai.rdoc
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
Dir {
|
2
|
+
ArchiveDir "<%= repositories_path %>/<%= metadata[:repository_prefix] %>";
|
3
|
+
CacheDir "<%= cache_path %>";
|
4
|
+
};
|
5
|
+
|
6
|
+
Tree "dists/<%= metadata[:variant] %>" {
|
7
|
+
Sections "10gen";
|
8
|
+
Architectures "i386 amd64";
|
9
|
+
};
|
10
|
+
|
11
|
+
APT::FTPArchive {
|
12
|
+
MD5 true;
|
13
|
+
SHA1 true;
|
14
|
+
SHA256 true;
|
15
|
+
SHA512 true;
|
16
|
+
Release {
|
17
|
+
Origin "10gen";
|
18
|
+
Label "10gen";
|
19
|
+
Suite "10gen";
|
20
|
+
Version "<%= metadata[:variant] %>";
|
21
|
+
Codename "<%= metadata[:variant] %>";
|
22
|
+
Architectures "i386 amd64";
|
23
|
+
Components "10gen";
|
24
|
+
Description "10gen packages";
|
25
|
+
};
|
26
|
+
};
|
metadata
ADDED
@@ -0,0 +1,198 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: melai
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Mike Fiedler
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-07-16 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: gli
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ! '>='
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '0'
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: versionomy
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ! '>='
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: '0'
|
38
|
+
type: :runtime
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ! '>='
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: '0'
|
46
|
+
- !ruby/object:Gem::Dependency
|
47
|
+
name: mixlib-shellout
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
50
|
+
requirements:
|
51
|
+
- - ! '>='
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '0'
|
54
|
+
type: :runtime
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ! '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
- !ruby/object:Gem::Dependency
|
63
|
+
name: rake
|
64
|
+
requirement: !ruby/object:Gem::Requirement
|
65
|
+
none: false
|
66
|
+
requirements:
|
67
|
+
- - ! '>='
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: '0'
|
70
|
+
type: :development
|
71
|
+
prerelease: false
|
72
|
+
version_requirements: !ruby/object:Gem::Requirement
|
73
|
+
none: false
|
74
|
+
requirements:
|
75
|
+
- - ! '>='
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
version: '0'
|
78
|
+
- !ruby/object:Gem::Dependency
|
79
|
+
name: aruba
|
80
|
+
requirement: !ruby/object:Gem::Requirement
|
81
|
+
none: false
|
82
|
+
requirements:
|
83
|
+
- - ! '>='
|
84
|
+
- !ruby/object:Gem::Version
|
85
|
+
version: '0'
|
86
|
+
type: :development
|
87
|
+
prerelease: false
|
88
|
+
version_requirements: !ruby/object:Gem::Requirement
|
89
|
+
none: false
|
90
|
+
requirements:
|
91
|
+
- - ! '>='
|
92
|
+
- !ruby/object:Gem::Version
|
93
|
+
version: '0'
|
94
|
+
- !ruby/object:Gem::Dependency
|
95
|
+
name: aruba-doubles
|
96
|
+
requirement: !ruby/object:Gem::Requirement
|
97
|
+
none: false
|
98
|
+
requirements:
|
99
|
+
- - ! '>='
|
100
|
+
- !ruby/object:Gem::Version
|
101
|
+
version: '0'
|
102
|
+
type: :development
|
103
|
+
prerelease: false
|
104
|
+
version_requirements: !ruby/object:Gem::Requirement
|
105
|
+
none: false
|
106
|
+
requirements:
|
107
|
+
- - ! '>='
|
108
|
+
- !ruby/object:Gem::Version
|
109
|
+
version: '0'
|
110
|
+
- !ruby/object:Gem::Dependency
|
111
|
+
name: pry
|
112
|
+
requirement: !ruby/object:Gem::Requirement
|
113
|
+
none: false
|
114
|
+
requirements:
|
115
|
+
- - ! '>='
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '0'
|
118
|
+
type: :development
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
none: false
|
122
|
+
requirements:
|
123
|
+
- - ! '>='
|
124
|
+
- !ruby/object:Gem::Version
|
125
|
+
version: '0'
|
126
|
+
- !ruby/object:Gem::Dependency
|
127
|
+
name: tailor
|
128
|
+
requirement: !ruby/object:Gem::Requirement
|
129
|
+
none: false
|
130
|
+
requirements:
|
131
|
+
- - ! '>='
|
132
|
+
- !ruby/object:Gem::Version
|
133
|
+
version: '0'
|
134
|
+
type: :development
|
135
|
+
prerelease: false
|
136
|
+
version_requirements: !ruby/object:Gem::Requirement
|
137
|
+
none: false
|
138
|
+
requirements:
|
139
|
+
- - ! '>='
|
140
|
+
- !ruby/object:Gem::Version
|
141
|
+
version: '0'
|
142
|
+
description: Build your repositories with melai
|
143
|
+
email:
|
144
|
+
- miketheman@gmail.com
|
145
|
+
executables:
|
146
|
+
- melai
|
147
|
+
extensions: []
|
148
|
+
extra_rdoc_files: []
|
149
|
+
files:
|
150
|
+
- .gitignore
|
151
|
+
- Gemfile
|
152
|
+
- LICENSE
|
153
|
+
- README.md
|
154
|
+
- Rakefile
|
155
|
+
- bin/melai
|
156
|
+
- features/directories.feature
|
157
|
+
- features/repository.feature
|
158
|
+
- features/step_definitions/directories_steps.rb
|
159
|
+
- features/support/env.rb
|
160
|
+
- lib/melai.rb
|
161
|
+
- lib/melai/dir_helpers.rb
|
162
|
+
- lib/melai/package_helpers.rb
|
163
|
+
- lib/melai/string_helpers.rb
|
164
|
+
- lib/melai/version.rb
|
165
|
+
- melai.gemspec
|
166
|
+
- melai.rdoc
|
167
|
+
- templates/apt-ftparchive.conf.erb
|
168
|
+
- templates/debian.list.erb
|
169
|
+
- templates/redhat.repo.erb
|
170
|
+
homepage: http://www.miketheman.net
|
171
|
+
licenses: []
|
172
|
+
post_install_message:
|
173
|
+
rdoc_options: []
|
174
|
+
require_paths:
|
175
|
+
- lib
|
176
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
177
|
+
none: false
|
178
|
+
requirements:
|
179
|
+
- - ! '>='
|
180
|
+
- !ruby/object:Gem::Version
|
181
|
+
version: '0'
|
182
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
183
|
+
none: false
|
184
|
+
requirements:
|
185
|
+
- - ! '>='
|
186
|
+
- !ruby/object:Gem::Version
|
187
|
+
version: '0'
|
188
|
+
requirements: []
|
189
|
+
rubyforge_project:
|
190
|
+
rubygems_version: 1.8.24
|
191
|
+
signing_key:
|
192
|
+
specification_version: 3
|
193
|
+
summary: melai builds multi-platform repositories for a given list of package files
|
194
|
+
test_files:
|
195
|
+
- features/directories.feature
|
196
|
+
- features/repository.feature
|
197
|
+
- features/step_definitions/directories_steps.rb
|
198
|
+
- features/support/env.rb
|