melai 0.0.1
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.
- 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
|