right_publish 0.3.0 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile +2 -0
- data/Gemfile.lock +2 -0
- data/README.rdoc +25 -25
- data/VERSION +1 -1
- data/bin/right_publish +1 -5
- data/lib/right_publish/annotation.rb +101 -0
- data/lib/right_publish/cli.rb +91 -0
- data/lib/right_publish/profile.rb +30 -20
- data/lib/right_publish/repo.rb +74 -25
- data/lib/right_publish/repos/apt.rb +8 -10
- data/lib/right_publish/repos/gem.rb +15 -11
- data/lib/right_publish/repos/msi.rb +45 -0
- data/lib/right_publish/repos/yum.rb +8 -11
- data/lib/right_publish/storage.rb +42 -3
- data/lib/right_publish/stores/local.rb +3 -3
- data/lib/right_publish.rb +24 -86
- data/right_publish.gemspec +13 -3
- data/spec/annotation_spec.rb +22 -0
- data/spec/repo_manager_spec.rb +1 -3
- data/spec/repo_spec.rb +5 -7
- data/spec/repos/apt_spec.rb +3 -3
- data/spec/repos/gem_spec.rb +3 -3
- data/spec/repos/yum_spec.rb +3 -3
- data/spec/spec_helper.rb +7 -0
- data/spec/storage_manager_spec.rb +0 -1
- data/spec/storage_spec.rb +26 -2
- data/spec/stores/local_spec.rb +0 -1
- data/spec/stores/s3_spec.rb +0 -1
- metadata +87 -52
data/Gemfile
CHANGED
@@ -2,6 +2,8 @@ source "http://rubygems.org"
|
|
2
2
|
|
3
3
|
gem "builder"
|
4
4
|
gem "fog", "~> 1.9"
|
5
|
+
gem "excon", "<= 0.25.3" # We do not depend directly on excon, but fog does, and > 0.25 is incompatible with fog
|
6
|
+
gem "mime-types", "~> 1.0"
|
5
7
|
gem "trollop", "~> 2.0"
|
6
8
|
|
7
9
|
# Gems used to test and develop RightPublish
|
data/Gemfile.lock
CHANGED
data/README.rdoc
CHANGED
@@ -33,28 +33,28 @@ debian distros. It expects the "expect" package to be installed on all distros.
|
|
33
33
|
# value of an attribute, or they can appear anywhere in
|
34
34
|
# the attribute with static text surrounding them.
|
35
35
|
#
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
:
|
55
|
-
|
56
|
-
|
57
|
-
|
36
|
+
verbose: # -: false
|
37
|
+
apt_repo:
|
38
|
+
dists: # *: [ woody, sid, etch, jaunty, lucid ]
|
39
|
+
auto: # -: true
|
40
|
+
subdir: # -: apt/
|
41
|
+
gpg_key_id: # *: 9A917D05
|
42
|
+
gpg_password: # *: @@GPG_PASSWORD@@
|
43
|
+
gem_repo:
|
44
|
+
subdir: # -: gems/
|
45
|
+
yum_repo:
|
46
|
+
dists: # *: el: ['6']
|
47
|
+
epel: # -: 1
|
48
|
+
subdir: # -: yum/
|
49
|
+
gpg_key_id: # *: 9A917D05
|
50
|
+
gpg_password: # *: @@GPG_PASSWORD@@
|
51
|
+
local_storage:
|
52
|
+
cache_dir # -: ~/.right_publish/cache
|
53
|
+
remote_storage:
|
54
|
+
provider: # -: s3
|
55
|
+
access_id: # *: @@AWS_ACCESS_KEY_ID@@
|
56
|
+
access_key: # *: @@AWS_SECRET_ACCESS_KEY@@
|
57
|
+
remote_path: # +: rightlink-testing
|
58
58
|
#
|
59
59
|
# End of RightPublish Profile
|
60
60
|
###########################################################
|
@@ -69,12 +69,12 @@ debian distros. It expects the "expect" package to be installed on all distros.
|
|
69
69
|
Usage:
|
70
70
|
right_publish [global_options] <command> [cmd_options]
|
71
71
|
commands:
|
72
|
-
|
72
|
+
pull
|
73
73
|
publish
|
74
|
-
|
74
|
+
push
|
75
75
|
global options:
|
76
76
|
--profile, -p <s>: Publish profile
|
77
|
-
--repo-type, -r <s>: Repository type: ["apt", "gem", "yum"]
|
77
|
+
--repo-type, -r <s>: Repository type: ["apt", "gem", "msi", "yum", "zypp"]
|
78
78
|
--dist, -d: Target dist: ["el/6", "precise"]
|
79
79
|
--verbose, -v: Verbose output
|
80
80
|
--version, -e: Print version and exit
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.
|
1
|
+
0.4.0
|
data/bin/right_publish
CHANGED
@@ -0,0 +1,101 @@
|
|
1
|
+
require 'builder'
|
2
|
+
|
3
|
+
RightPublish::Profile.instance.register_section(:annotation,
|
4
|
+
:title => nil,
|
5
|
+
:scripts => [],
|
6
|
+
:stylesheets => [])
|
7
|
+
|
8
|
+
module RightPublish
|
9
|
+
module Annotation
|
10
|
+
module_function
|
11
|
+
|
12
|
+
# Generate an HTML index file for a set of packages.
|
13
|
+
# @param [Array] files list of relative file keys
|
14
|
+
# @param [Integer] strip number of path elements to strip from displayed filenames, default 0
|
15
|
+
# @option options [Array] :filter a whitelist of String glob patterns to filter files, i.e. ["*.rpm", "*.deb"]
|
16
|
+
# @option options [String] :title Human-readable page title
|
17
|
+
# @option options [Array] :scripts JavaScript hrefs to embed into page as <script> tags
|
18
|
+
# @option options [Array] :stylesheets Stylesheet hrefs to embed into page as <link> tags
|
19
|
+
def generate_html(files, strip=0, options={})
|
20
|
+
title = options[:title]
|
21
|
+
filter = options[:filter] || []
|
22
|
+
scripts = options[:scripts] || []
|
23
|
+
stylesheets = options[:stylesheets] || []
|
24
|
+
|
25
|
+
unless filter.nil? || filter.empty?
|
26
|
+
filtered = files.select { |f| filter.any? { |ff| File.fnmatch(ff, f.key) } }
|
27
|
+
end
|
28
|
+
segmented = filtered.map { |f| f.key.split('/') }
|
29
|
+
|
30
|
+
index = {}
|
31
|
+
segmented.each do |segments|
|
32
|
+
stripped = segments[strip..-1]
|
33
|
+
|
34
|
+
map = index
|
35
|
+
stripped.each_with_index do |seg, idx|
|
36
|
+
if idx == stripped.size - 1
|
37
|
+
map[seg] = "/#{segments.join('/')}"
|
38
|
+
else
|
39
|
+
map[seg] ||= {}
|
40
|
+
map = map[seg]
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
b = Builder::XmlMarkup.new(:indent => 2)
|
46
|
+
|
47
|
+
# Declare us as an HTML5 document
|
48
|
+
b.declare!(:DOCTYPE, :HTML)
|
49
|
+
|
50
|
+
b.html {
|
51
|
+
b.head {
|
52
|
+
# Ensure that browsers get a valid content type and charset, even if the Web server
|
53
|
+
# is misconfigured. This is necessary for our doc to be considered valid HTML5.
|
54
|
+
b.meta(:'http-equiv'=>'Content-Type', :content=>'text/html; charset=UTF-8')
|
55
|
+
|
56
|
+
if title
|
57
|
+
b.title(title)
|
58
|
+
end
|
59
|
+
|
60
|
+
stylesheets.each do |stylesheet|
|
61
|
+
b.link(:rel => 'stylesheet', :type => 'text/css', :href => stylesheet)
|
62
|
+
end
|
63
|
+
|
64
|
+
scripts.each do |script|
|
65
|
+
b.script(:src => script, :type => 'text/javascript')
|
66
|
+
end
|
67
|
+
}
|
68
|
+
b.body {
|
69
|
+
if title
|
70
|
+
b.h1(title, :class => 'title')
|
71
|
+
end
|
72
|
+
recursive_ul(index, b)
|
73
|
+
}
|
74
|
+
}
|
75
|
+
end
|
76
|
+
|
77
|
+
private
|
78
|
+
|
79
|
+
def self.recursive_ul(index, b)
|
80
|
+
b.ul(:class => 'dirList') {
|
81
|
+
index.keys.sort.each do |k|
|
82
|
+
v = index[k]
|
83
|
+
|
84
|
+
case v
|
85
|
+
when Hash
|
86
|
+
b.li(:class => 'dir') {
|
87
|
+
b.text! k
|
88
|
+
recursive_ul(v, b)
|
89
|
+
}
|
90
|
+
else
|
91
|
+
base = File.basename(v)
|
92
|
+
ext = File.extname(v)[1..-1]
|
93
|
+
b.li(:class => ext) {
|
94
|
+
b.a(base, :href => v)
|
95
|
+
}
|
96
|
+
end
|
97
|
+
end
|
98
|
+
}
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
@@ -0,0 +1,91 @@
|
|
1
|
+
require 'trollop'
|
2
|
+
|
3
|
+
module RightPublish
|
4
|
+
module CLI
|
5
|
+
REPOSITORY_TYPES = RepoManager.repo_types()
|
6
|
+
SUB_COMMANDS = %w(publish pull add annotate push)
|
7
|
+
|
8
|
+
def self.run()
|
9
|
+
options = parse_args
|
10
|
+
|
11
|
+
begin
|
12
|
+
profile = RightPublish::Profile.instance
|
13
|
+
profile.load(options[:profile] || options[:repo_type])
|
14
|
+
rescue LoadError => e
|
15
|
+
puts e
|
16
|
+
exit -1
|
17
|
+
end
|
18
|
+
|
19
|
+
# These options override profile attributes.
|
20
|
+
profile.settings[:local_storage][:cache_dir] = options[:local_cache] if options[:local_cache]
|
21
|
+
profile.settings[:verbose] = true if options[:verbose]
|
22
|
+
|
23
|
+
# We already know this is a valid type, parse_args checked
|
24
|
+
repo = RepoManager.get_repository(REPOSITORY_TYPES[options[:repo_type]])
|
25
|
+
begin
|
26
|
+
case options[:cmd]
|
27
|
+
when 'publish'
|
28
|
+
repo.publish(options[:files], options[:dist])
|
29
|
+
when 'pull'
|
30
|
+
repo.pull
|
31
|
+
when 'add'
|
32
|
+
repo.add(options[:files], options[:dist])
|
33
|
+
when 'annotate'
|
34
|
+
repo.annotate
|
35
|
+
when 'push'
|
36
|
+
repo.push
|
37
|
+
else
|
38
|
+
raise ArgumentError, "Unknown command '#{options[:cmd]}'"
|
39
|
+
end
|
40
|
+
rescue RuntimeError => e
|
41
|
+
RightPublish::Profile.log("Fatal Error:\n\t#{e}", :error)
|
42
|
+
exit -1
|
43
|
+
end
|
44
|
+
|
45
|
+
exit 0
|
46
|
+
end
|
47
|
+
|
48
|
+
def self.parse_args()
|
49
|
+
options = Trollop.options do
|
50
|
+
version "RightPublish (c) 2013 RightScale Inc"
|
51
|
+
banner <<-EOS
|
52
|
+
RightPublish can manage a YUM/APT/RubyGem repository in remote storage, e.g. S3.
|
53
|
+
|
54
|
+
Usage:
|
55
|
+
right_publish [options] <command> [file1, file2, ...]
|
56
|
+
|
57
|
+
basic commands:
|
58
|
+
publish
|
59
|
+
|
60
|
+
advanced commands:
|
61
|
+
pull add annotate push
|
62
|
+
|
63
|
+
options:
|
64
|
+
EOS
|
65
|
+
|
66
|
+
opt :local_cache, "Local cache location", :type => String
|
67
|
+
opt :profile, "Publish profile", :type => String, :default => nil
|
68
|
+
opt :repo_type, "Repository type: #{REPOSITORY_TYPES.keys.inspect}", :type => String
|
69
|
+
opt :dist, "Target distribution. Required for binary packages. If unspecified for noarch and source packages, will copy to all distributions specified in profile.", :type => String
|
70
|
+
opt :verbose, "Verbose output"
|
71
|
+
end
|
72
|
+
|
73
|
+
options[:cmd]= ARGV.shift
|
74
|
+
options[:files] = expand_argv_globs
|
75
|
+
|
76
|
+
Trollop.die "invalid repository type: #{options[:repo_type]}" unless REPOSITORY_TYPES[options[:repo_type]]
|
77
|
+
|
78
|
+
options
|
79
|
+
end
|
80
|
+
|
81
|
+
def self.expand_argv_globs
|
82
|
+
files = []
|
83
|
+
|
84
|
+
ARGV.each do |glob|
|
85
|
+
files += Dir.glob(glob)
|
86
|
+
end
|
87
|
+
|
88
|
+
files
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
@@ -2,13 +2,10 @@ require 'singleton'
|
|
2
2
|
require 'yaml'
|
3
3
|
|
4
4
|
module RightPublish
|
5
|
-
|
6
5
|
class Profile
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
DEFAULT_VERBOSE = false
|
11
|
-
# =============
|
6
|
+
# Location where per-user profiles are stored
|
7
|
+
USER_PROFILE_DIR = File.expand_path('~/.right_publish/profiles')
|
8
|
+
DEFAULT_VERBOSE = false
|
12
9
|
|
13
10
|
attr_accessor :settings
|
14
11
|
include Singleton
|
@@ -21,22 +18,35 @@ module RightPublish
|
|
21
18
|
puts(s) if Profile.config[:verbose] || level != :debug
|
22
19
|
end
|
23
20
|
|
21
|
+
# Load a YML profile from disk. Profiles can be identified by their path on disk, or by the name
|
22
|
+
# of a YML file located in USER_PROFILE_DIR (without YML extension).
|
23
|
+
#
|
24
|
+
# @param [String] path absolute/relative path to file, or the name of per-user profile
|
25
|
+
# @return [true] always returns true
|
26
|
+
#
|
27
|
+
# @example per-user profile
|
28
|
+
# Profile.instance.load("gem")
|
29
|
+
#
|
30
|
+
# @example profile on disk
|
31
|
+
# Profile.instance.load("config/publish/nightly.yml")
|
32
|
+
#
|
33
|
+
# @see USER_PROFILE_DIR
|
24
34
|
def load(path)
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
else
|
34
|
-
raise LoadError.new("Missing Profile")
|
35
|
-
end
|
36
|
-
rescue LoadError => e
|
37
|
-
raise LoadError.new("#{e}")
|
35
|
+
if File.exists?(path)
|
36
|
+
@settings ||= Profile.symbolize_profile(YAML.load_file(path))
|
37
|
+
validate_profile
|
38
|
+
elsif (path = File.join(USER_PROFILE_DIR, "#{path}.yml")) && File.exists?(path)
|
39
|
+
@settings ||= Profile.symbolize_profile(YAML.load_file(path))
|
40
|
+
validate_profile
|
41
|
+
else
|
42
|
+
raise LoadError, "Missing profile '#{path}'"
|
38
43
|
end
|
39
|
-
|
44
|
+
|
45
|
+
true
|
46
|
+
rescue LoadError
|
47
|
+
raise
|
48
|
+
rescue Exception => e
|
49
|
+
raise LoadError, "Cannot load profile '#{path}' - #{e}"
|
40
50
|
end
|
41
51
|
|
42
52
|
def register_section(section_key, section_options)
|
data/lib/right_publish/repo.rb
CHANGED
@@ -5,7 +5,7 @@ module RightPublish
|
|
5
5
|
|
6
6
|
class RepoManager
|
7
7
|
@@repository_type_regex = /\A(\w+)_repo/
|
8
|
-
@@repository_table
|
8
|
+
@@repository_table = {}
|
9
9
|
|
10
10
|
def self.get_repository(type)
|
11
11
|
@@repository_table[type].new(type) if @@repository_table[type]
|
@@ -31,37 +31,85 @@ module RightPublish
|
|
31
31
|
end
|
32
32
|
end
|
33
33
|
|
34
|
-
|
34
|
+
class Repo
|
35
35
|
def initialize(option_key)
|
36
36
|
@repository_type ||= option_key
|
37
37
|
end
|
38
38
|
|
39
|
-
|
40
|
-
|
39
|
+
# Sync files from remote to local storage
|
40
|
+
def pull()
|
41
|
+
Profile.log("Fetching #{repo_human_name(@repository_type)} files from remote...")
|
42
|
+
|
41
43
|
begin
|
42
44
|
sync_dirs(
|
43
45
|
get_storage(Profile.config[:remote_storage][:provider]),
|
44
46
|
get_storage(Profile.config[:local_storage][:provider]),
|
45
|
-
Profile.config[@repository_type][:subdir]
|
47
|
+
Profile.config[@repository_type][:subdir])
|
46
48
|
rescue Exception => e
|
47
49
|
RightPublish::Profile.log("Could not synchronize storage:\n\t#{e}", :error)
|
48
|
-
raise RuntimeError, "
|
50
|
+
raise RuntimeError, "pull from remote failed."
|
49
51
|
end
|
50
52
|
end
|
51
53
|
|
52
|
-
|
53
|
-
|
54
|
+
# Sync files from local to remote storage
|
55
|
+
def push()
|
56
|
+
Profile.log("Committing local #{repo_human_name(@repository_type)} files to remote...")
|
57
|
+
|
54
58
|
begin
|
55
59
|
sync_dirs(
|
56
60
|
get_storage(Profile.config[:local_storage][:provider]),
|
57
61
|
get_storage(Profile.config[:remote_storage][:provider]),
|
58
|
-
Profile.config[@repository_type][:subdir]
|
62
|
+
Profile.config[@repository_type][:subdir])
|
59
63
|
rescue Exception => e
|
60
64
|
RightPublish::Profile.log("Could not sychronize storage:\n\t#{e}", :error)
|
61
|
-
raise RuntimeError, "
|
65
|
+
raise RuntimeError, "push to remote failed."
|
62
66
|
end
|
63
67
|
end
|
64
|
-
|
68
|
+
|
69
|
+
# Create an index.html file to enable browsing of this repo's subdir.
|
70
|
+
# @option options [String] :subdir a subdirectory (common prefix); only files matching this prefix will be included
|
71
|
+
# @option options [Array] :filter a whitelist of String glob patterns to filter files, i.e. ["*.rpm", "*.deb"]
|
72
|
+
def annotate(options={})
|
73
|
+
|
74
|
+
options[:subdir] ||= repo_config[:subdir]
|
75
|
+
storage_type = options[:use_remote_storage] ? :remote_storage : :local_storage
|
76
|
+
|
77
|
+
Profile.log("Creating HTML directory listing from #{storage_type.to_s} storage for #{repo_human_name(@repository_type)} files...")
|
78
|
+
|
79
|
+
files = []
|
80
|
+
RightPublish::Storage.ls(
|
81
|
+
get_storage(Profile.config[storage_type][:provider]),
|
82
|
+
:subdir => options[:subdir]) do |file|
|
83
|
+
files << file
|
84
|
+
end
|
85
|
+
|
86
|
+
# Build merged options hash for annotation, based on profile config plus some options
|
87
|
+
# passed into our class.
|
88
|
+
html_options = Profile.config[:annotation] ? Profile.config[:annotation].dup : {}
|
89
|
+
html_options[:filter] = options[:filter] if options.key?(:filter)
|
90
|
+
|
91
|
+
strip = options[:subdir].split('/').size
|
92
|
+
html = RightPublish::Annotation.generate_html(files, strip, html_options)
|
93
|
+
|
94
|
+
output = File.join(repo_config[:subdir], 'index.html')
|
95
|
+
local_dir = get_storage(Profile.config[:local_storage][:provider]).get_directories
|
96
|
+
local_dir.files.create(:key => output, :body => html)
|
97
|
+
end
|
98
|
+
|
99
|
+
# Perform one-shot publish of one or more packages.
|
100
|
+
# "One-shot" means: pull, add, annotate and push.
|
101
|
+
def publish(file_or_dir, target)
|
102
|
+
pull
|
103
|
+
add(file_or_dir, target)
|
104
|
+
annotate
|
105
|
+
push
|
106
|
+
end
|
107
|
+
|
108
|
+
# Add a new package to local storage and reindex if necessary
|
109
|
+
def add(file_or_dir, targe)
|
110
|
+
raise NotImplementedError, "Subclass responsibility"
|
111
|
+
end
|
112
|
+
|
65
113
|
private
|
66
114
|
|
67
115
|
def build_glob(ext)
|
@@ -77,16 +125,16 @@ module RightPublish
|
|
77
125
|
end
|
78
126
|
|
79
127
|
def get_pkg_list(file_or_dir, ext=nil)
|
80
|
-
pkg_list
|
128
|
+
pkg_list = []
|
81
129
|
file_or_dir = Array(file_or_dir)
|
82
130
|
file_or_dir.each do |path|
|
83
131
|
path = path.gsub("\\", "/")
|
84
132
|
pkg_list << if File.directory?(path)
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
133
|
+
glob_filter = (ext && "*.#{build_glob(ext)}") || "*"
|
134
|
+
Dir.glob(File.join(path, glob_filter))
|
135
|
+
else
|
136
|
+
path.split(',')
|
137
|
+
end
|
90
138
|
end
|
91
139
|
pkg_list.flatten!
|
92
140
|
|
@@ -100,8 +148,8 @@ module RightPublish
|
|
100
148
|
pkg_list
|
101
149
|
end
|
102
150
|
|
103
|
-
# For automation, we want to send the password, however gpg uses getpass
|
104
|
-
# c function, which interacts directly with /dev/pty instead of stdin/stdout
|
151
|
+
# For automation, we want to send the password, however gpg uses getpass
|
152
|
+
# c function, which interacts directly with /dev/pty instead of stdin/stdout
|
105
153
|
# to hide the password as its typed in. So, we need to allocate a pty.
|
106
154
|
# We do this by shelling out to expect script (tcl based). Ruby 1.8
|
107
155
|
# implementation of "expect" is broken and has some race conditions with
|
@@ -109,9 +157,9 @@ module RightPublish
|
|
109
157
|
def shellout_with_password(cmd)
|
110
158
|
password = repo_config[:gpg_password]
|
111
159
|
raise Exception, ":gpg_password must be supplied when signing packages" unless password
|
112
|
-
ENV['GPG_PASSWORD'] = password
|
113
|
-
bin_dir
|
114
|
-
autosign
|
160
|
+
ENV['GPG_PASSWORD'] = password
|
161
|
+
bin_dir = File.expand_path("../../../bin", __FILE__)
|
162
|
+
autosign = File.join(bin_dir, "autosign.expect")
|
115
163
|
system("#{autosign} #{cmd}")
|
116
164
|
end
|
117
165
|
|
@@ -123,7 +171,7 @@ module RightPublish
|
|
123
171
|
def install_file(file, dest)
|
124
172
|
Profile.log("#{file} => #{dest}")
|
125
173
|
local_dir = get_storage(Profile.config[:local_storage][:provider]).get_directories
|
126
|
-
File.open(file, "rb") { |chunk| local_dir.files.create(:key=>File.join(dest, File.basename(file)), :body=>chunk) }
|
174
|
+
File.open(file, "rb") { |chunk| local_dir.files.create(:key => File.join(dest, File.basename(file)), :body => chunk) }
|
127
175
|
end
|
128
176
|
|
129
177
|
def prune_all(glob_pattern)
|
@@ -136,16 +184,17 @@ module RightPublish
|
|
136
184
|
end
|
137
185
|
|
138
186
|
def repo_human_name(type)
|
139
|
-
type.to_s.sub(/_/,' ')
|
187
|
+
type.to_s.sub(/_/, ' ')
|
140
188
|
end
|
141
189
|
|
142
190
|
def sync_dirs(src, dest, subdir='')
|
143
|
-
RightPublish::Storage.sync_dirs(src, dest, :subdir=>subdir, :sweep=>true)
|
191
|
+
RightPublish::Storage.sync_dirs(src, dest, :subdir => subdir, :sweep => true)
|
144
192
|
end
|
145
193
|
end
|
146
194
|
end
|
147
195
|
|
148
196
|
require 'right_publish/repos/apt'
|
149
197
|
require 'right_publish/repos/gem'
|
198
|
+
require 'right_publish/repos/msi'
|
150
199
|
require 'right_publish/repos/yum'
|
151
200
|
require 'right_publish/repos/zypp'
|