right_publish 0.3.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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'
|