right_publish 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,178 @@
1
+ module RightPublish
2
+ class AptRepo
3
+ include RightPublish::Repo
4
+
5
+ DEFAULT_APT_AUTO = true
6
+ DEFAULT_APT_DIR = 'apt/'
7
+ DEFAULT_DESCRIPTION = "RightScale RightLink Repository"
8
+ REPO_KEY = :apt_repo
9
+
10
+ REPO_OPTIONS = {
11
+ :dists=>:addr_optional,
12
+ :description => DEFAULT_DESCRIPTION,
13
+ :auto=>DEFAULT_APT_AUTO,
14
+ :subdir=>DEFAULT_APT_DIR,
15
+ :gpg_key_id => :attr_optional,
16
+ :gpg_password => :attr_optional }
17
+
18
+ BIN_EXTENSION = 'deb'
19
+ SRC_EXTENSION = 'dsc'
20
+ BIN_ALL_ARCH = 'all'
21
+
22
+ @@supported_archs = [ 'i386', 'amd64' ]
23
+ @@all_archs = [ 'i386', 'amd64', BIN_ALL_ARCH ]
24
+
25
+ def publish(file_or_dir, target)
26
+ repo_updates = {}
27
+
28
+ # If we specified a target, let's make sure it's in our profile
29
+ if target
30
+ fail("specified distribution is not listed in this profile!") unless repo_config[:dists].include?(target)
31
+ end
32
+
33
+ pkgs = []
34
+ get_pkg_list(file_or_dir, [BIN_EXTENSION, SRC_EXTENSION]) do |path|
35
+ if path.end_with? BIN_EXTENSION and repo_config[:auto]
36
+ arch = AptRepo.pkg_parts(path)[:arch] || fail("could not determine architecture for package: #{path}")
37
+ fail("you probably want to specify a distribution target for binary packages!") unless target || arch == BIN_ALL_ARCH
38
+ end
39
+ pkgs << path
40
+ end
41
+
42
+ # Synchronize the upstream source to our local cache
43
+ fetch
44
+
45
+ repo_path = File.join(Profile.config[:local_storage][:cache_dir], repo_config[:subdir])
46
+ write_conf_file repo_path if repo_config[:auto]
47
+
48
+ pkgs.each do |pkg|
49
+ Profile.log("Adding package [#{pkg}]...")
50
+ if repo_config[:auto]
51
+ # If source package, remove existing sources so we don't get a hash clash
52
+ auto_repo_remove(repo_path, pkg, target)
53
+ auto_repo_add(repo_path, pkg, target)
54
+ else
55
+ trivial_repo_add(pkg)
56
+ end
57
+ end
58
+ rebuild_index(repo_config[:subdir]) if not repo_config[:auto]
59
+
60
+ # Commit back to remote storage
61
+ store
62
+ end
63
+
64
+ def self.pkg_parts(pkg_name)
65
+ result = {:name=>nil, :version=>nil, :arch=>nil, :ext=>nil}
66
+ if pkg_name.end_with?(SRC_EXTENSION)
67
+ if /([A-Za-z0-9\.\-+]+)_([A-Za-z0-9\-\.:~]+)\.#{SRC_EXTENSION}\Z/.match(pkg_name)
68
+ result[:name] = $1
69
+ result[:version] = $2
70
+ result[:ext] = SRC_EXTENSION
71
+ end
72
+ else
73
+ if /([A-Za-z0-9\.\-+]+)_([A-Za-z0-9\-\.:~]+)_(#{@@all_archs.join('|')})\.#{BIN_EXTENSION}\Z/.match(pkg_name)
74
+ result[:name] = $1
75
+ result[:version] = $2
76
+ result[:arch] = $3
77
+ result[:ext] = BIN_EXTENSION
78
+ end
79
+ end
80
+ result
81
+ end
82
+
83
+ private
84
+
85
+ def auto_repo_add(repo_path, pkg, target)
86
+ targets = (target && Array(target)) || repo_config[:dists]
87
+ targets.each do |t|
88
+ sub_command = (pkg.end_with?(BIN_EXTENSION) && 'includedeb') || 'includedsc'
89
+ ask_passphrase = (repo_config[:gpg_key_id]) ? "--ask-passphrase " : ""
90
+ cmd = "reprepro #{ask_passphrase}-C main -b #{repo_path} #{sub_command} #{t} #{pkg} 2>&1 >/dev/null"
91
+ if repo_config[:gpg_key_id]
92
+ exited = shellout_with_password(cmd)
93
+ else
94
+ exited = system(cmd)
95
+ end
96
+
97
+ raise RuntimeError, "apt package installation failed; cannot continue publishing" unless exited
98
+ end
99
+ end
100
+
101
+ def auto_repo_remove(repo_path, pkg, target)
102
+ sub_command = (pkg.end_with?(BIN_EXTENSION) && 'remove') || 'removesrc'
103
+ pkg_name = AptRepo.pkg_parts(pkg)[:name]
104
+ Profile.log("Removing any existing files for #{pkg_name}")
105
+
106
+ targets = (target && Array(target)) || repo_config[:dists]
107
+ targets.each do |t|
108
+ ask_passphrase = (repo_config[:gpg_key_id]) ? "--ask-passphrase" : ""
109
+ cmd = "reprepro #{ask_passphrase} -b #{repo_path} #{sub_command} #{t} #{pkg_name} 2>&1 >/dev/null"
110
+ if repo_config[:gpg_key_id]
111
+ exited = shellout_with_password(cmd)
112
+ else
113
+ exited = system(cmd)
114
+ end
115
+ end
116
+ end
117
+
118
+ def rebuild_index(subdir)
119
+ Profile.log("Rebuilding repository index...")
120
+ do_in_subdir(subdir) do
121
+ indexed = system("dpkg-scanpackages binaries /dev/null 2>/dev/null | gzip -c9 > Packages.gz")
122
+ raise RuntimeError, "apt package installation failed; cannot continue publishing" unless indexed
123
+ indexed = system("dpkg-scansources sources /dev/null 2>/dev/null | gzip -c9 > Sources.gz")
124
+ raise RuntimeError, "apt package installation failed; cannot continue publishing" unless indexed
125
+ end
126
+ end
127
+
128
+ def trivial_repo_add(pkg)
129
+ pkg_info = AptRepo.pkg_parts(pkg)
130
+
131
+ if pkg_info[:ext].eql?(BIN_EXTENSION)
132
+ sub_dir = File.join(repo_config[:subdir], 'binaries')
133
+ else
134
+ pkg_name = pkg_info[:name]
135
+ src_file = Dir.glob("#{File.dirname(pkg)}/#{pkg_name}_*.orig.tar.gz")
136
+ raise RuntimeError, "could not find original source for #{pkg}, missing or ambiguous." unless src_file.size == 1
137
+
138
+ diff_file = pkg.sub(/#{SRC_EXTENSION}$/, 'diff.gz')
139
+ sub_dir = File.join(repo_config[:subdir], 'sources')
140
+ raise RuntimeError, "missing the debian diff file for #{pkg}." unless File.file?(diff_file)
141
+
142
+ do_in_subdir(sub_dir) { prune_all("#{pkg_info[:name]}*.orig.tar.gz") }
143
+ install_file(src_file[0], sub_dir)
144
+
145
+ do_in_subdir(sub_dir) { prune_all("#{pkg_info[:name]}*.diff.gz") }
146
+ install_file(diff_file, sub_dir)
147
+ end
148
+
149
+ do_in_subdir(sub_dir) { prune_all("#{pkg_info[:name]}_*#{pkg_info[:arch]}.#{pkg_info[:ext]}") }
150
+ install_file(pkg, sub_dir)
151
+ end
152
+
153
+ def write_conf_file(repo_path)
154
+ destination_dir = File.expand_path(File.join(repo_path, 'conf'))
155
+
156
+ FileUtils.mkdir_p destination_dir
157
+ config_file = open( File.join(destination_dir, 'distributions'), 'wb' )
158
+
159
+ repo_config[:dists].each { |dist| config_file << dist_conf(dist) }
160
+ config_file.close
161
+ end
162
+
163
+ def dist_conf(dist)
164
+ <<EOF
165
+ Origin: RightScale
166
+ Codename: #{dist}
167
+ Version: 1.0
168
+ Architectures: #{@@supported_archs.join(' ')} source
169
+ Components: main
170
+ Description: #{repo_config[:description]}
171
+ #{(repo_config[:gpg_key_id] && "SignWith: #{repo_config[:gpg_key_id]}") || nil}
172
+
173
+ EOF
174
+ end
175
+ end
176
+
177
+ RightPublish::RepoManager.register_repo(AptRepo)
178
+ end
@@ -0,0 +1,44 @@
1
+ module RightPublish
2
+ class GemRepo
3
+ include RightPublish::Repo
4
+
5
+ DEFAULT_GEM_DIR = 'gems/'
6
+ REPO_KEY = :gem_repo
7
+ REPO_OPTIONS = {:subdir=>DEFAULT_GEM_DIR}
8
+
9
+ GEMS_SUBDIRECTORY = 'gems'
10
+ GEM_EXT = 'gem'
11
+
12
+ def publish(file_or_dir, target)
13
+ gems_list = get_pkg_list(file_or_dir, GEM_EXT)
14
+
15
+ # Synchronize the upstream source to our local cache
16
+ fetch
17
+
18
+ # Copy gem files to the repository
19
+ destination = File.join(repo_config[:subdir], GEMS_SUBDIRECTORY)
20
+ gems_list.each do |path|
21
+ do_in_subdir(destination) { prune_all("#{pkg_parts(path)[:name]}-*.gem") }
22
+ install_file(path, destination)
23
+ end
24
+
25
+ # Rebuild the gem index
26
+ Profile.log("Rebuilding Gem Index...")
27
+ do_in_subdir(repo_config[:subdir]) do
28
+ indexed = system('gem generate_index 2>&1 >/dev/null') # requires 'builder' gem to be installed
29
+ raise Exception, "gem generate_index failed; cannot continue publishing" unless indexed
30
+ end
31
+
32
+ # Commit back to remote storage
33
+ store
34
+ end
35
+
36
+ def pkg_parts(name)
37
+ result = {:name=>nil, :version=>nil}
38
+ result[:name], result[:version] = /([A-Za-z0-9\-_]+)-([0-9\.]+)\.#{GEM_EXT}\Z/.match(name).captures
39
+ result
40
+ end
41
+ end
42
+
43
+ RightPublish::RepoManager.register_repo(GemRepo)
44
+ end
@@ -0,0 +1,167 @@
1
+
2
+
3
+ module RightPublish
4
+
5
+ class YumRepo
6
+ include RightPublish::Repo
7
+
8
+ DEFAULT_YUM_EPEL = 1
9
+ DEFAULT_YUM_DIR = 'yum/'
10
+ DEFAULT_DESCRIPTION = "RightScale RightLink Repository"
11
+ REPO_KEY = :yum_repo
12
+ DEFAULT_EPEL_LAYOUT = "rightlink"
13
+ REPO_OPTIONS = {
14
+ :dists => :attr_optional,
15
+ :epel => DEFAULT_YUM_EPEL,
16
+ :epel_layout => DEFAULT_EPEL_LAYOUT,
17
+ :subdir => DEFAULT_YUM_DIR,
18
+ :description => DEFAULT_DESCRIPTION,
19
+ :gpg_key_id => :attr_optional,
20
+ :gpg_password => :attr_optional }
21
+ YUM_EXT = 'rpm'
22
+
23
+ BIN_ALL_ARCH = 'noarch'
24
+ SRC_ALL_ARCH = 'src'
25
+ SRC_ALL_PATH = 'SRPMS'
26
+
27
+ ARCHITECTURES = [ 'i386', 'x86_64' ]
28
+ PKG_TYPES = [ ARCHITECTURES, BIN_ALL_ARCH, SRC_ALL_ARCH ]
29
+
30
+ def publish(file_or_dir, target)
31
+ repo_updates = {}
32
+
33
+ get_pkg_list(file_or_dir, YUM_EXT) do |path|
34
+ # Determine package architectures, and sort
35
+ appropriate_repos(path, target) do |repo_path|
36
+ repo_path = File.join(repo_config[:subdir], repo_path)
37
+ repo_updates[repo_path] ||= []
38
+ repo_updates[repo_path].push(path)
39
+ end
40
+ end
41
+ return if repo_updates.size == 0
42
+
43
+ # Synchronize the upstream source to our local cache
44
+ fetch
45
+
46
+ full_pkg_paths = []
47
+ repo_updates.each_pair do |repo_path, pkgs|
48
+ pkgs.each do |pkg|
49
+ import_pkg( pkg, repo_path )
50
+ full_pkg_paths << File.join(repo_path, File.basename(pkg))
51
+ end
52
+ end
53
+ sign_files full_pkg_paths
54
+
55
+ # Rebuild the yum index'
56
+ repo_updates.each_key do |repo_path|
57
+ regen_metadata( repo_path )
58
+ end
59
+
60
+ # Commit back to remote storage
61
+ store
62
+ end
63
+
64
+ def import_pkg( pkg, repo_path )
65
+ # Remove any instances of this package at a different versionh
66
+ yum_prune( pkg, repo_path )
67
+ install_file( pkg, repo_path )
68
+ end
69
+
70
+ def sign_files( pkgs )
71
+ if repo_config[:gpg_key_id]
72
+ do_in_subdir('') do
73
+
74
+ cmd = "rpm --define '%_gpg_name #{repo_config[:gpg_key_id]}' --addsign #{pkgs.join(' ')} 2>&1 >/dev/null"
75
+ exited = shellout_with_password(cmd)
76
+
77
+ raise Exception, "rpm signing failed; cannot continue publishing" unless exited
78
+ end
79
+ end
80
+ end
81
+
82
+ def pkg_parts(path)
83
+ @infocache ||= {}
84
+ return @infocache[path] if @infocache.has_key?(path)
85
+
86
+ result = {:name=>nil, :version=>nil, :arch=>nil, :release=>nil}
87
+
88
+ query = IO.popen("rpm --queryformat '%{NAME} %{VERSION} %{RELEASE} %{ARCH}' -qp #{path}")
89
+ result[:name], result[:version], result[:release], result[:arch] = query.readline.split(' ')
90
+
91
+ # RPM query doesn't identify package as a source package, we just
92
+ # have to test the filename.
93
+ result[:arch] = SRC_ALL_ARCH if path.end_with?('src.rpm')
94
+
95
+ @infocache[path] = result
96
+ result
97
+ end
98
+
99
+ private
100
+
101
+ def repo_dir(epel_ver, dist, dist_ver, arch)
102
+ if repo_config[:epel_layout] == "rightscale-software"
103
+ File.join(dist_ver, arch)
104
+ else
105
+ File.join(epel_ver.to_s, dist, dist_ver, arch)
106
+ end
107
+ end
108
+
109
+ def appropriate_repos(path, target)
110
+ unless repo_config[:epel]
111
+ # This is a flat repo
112
+ yield ''
113
+ return
114
+ end
115
+
116
+ pkg_arch = pkg_parts(path)[:arch] || fail("could not determine architecture for package: #{path}")
117
+
118
+ if target
119
+ #TODO Check that the specified dist is configured in the profile (sanity)
120
+ dist, dist_ver = target.split("/")
121
+ fail("dist format needs to be in form 'dist/dist_version'!") unless dist && dist_ver
122
+ dists = {dist => [dist_ver]}
123
+ else
124
+ unless [BIN_ALL_ARCH,SRC_ALL_ARCH].include? pkg_arch
125
+ fail("need to specify a distribution with binary packages!")
126
+ end
127
+ dists = repo_config[:dists]
128
+ end
129
+
130
+ dists.each_pair do |dist,dist_vers|
131
+ dist_vers.each do |ver|
132
+ # Source RPMS stored in SRPMS architecture, noarchs go to all architectures
133
+ case pkg_arch
134
+ when SRC_ALL_ARCH
135
+ [SRC_ALL_PATH]
136
+ when BIN_ALL_ARCH
137
+ ARCHITECTURES
138
+ else
139
+ [pkg_arch]
140
+ end.each { |arch| yield repo_dir(repo_config[:epel].to_s, dist.to_s, ver.to_s, arch) }
141
+ end
142
+ end
143
+ end
144
+
145
+ def regen_metadata(repo_path)
146
+ # Rebuild the yum index
147
+ Profile.log("Rebuilding Yum Repo [#{repo_path}]...")
148
+ do_in_subdir(repo_path) do
149
+ exit_val = system('createrepo --update -o $(pwd) $(pwd) 2>&1 >/dev/null')
150
+ raise Exception, "yum regen_metadata failed; cannot continue publishing" unless exit_val
151
+ end
152
+ end
153
+
154
+ def yum_prune(pkg, repo_path)
155
+ our_name = pkg_parts(pkg)[:name]
156
+ our_arch = pkg_parts(pkg)[:arch]
157
+
158
+ do_in_subdir(repo_path) do
159
+ Dir.glob("*.#{YUM_EXT}") do |rpm|
160
+ File.unlink(rpm) if pkg_parts(rpm)[:name] == our_name && pkg_parts(rpm)[:arch] == our_arch
161
+ end
162
+ end
163
+ end
164
+ end
165
+
166
+ RightPublish::RepoManager.register_repo(YumRepo)
167
+ end
@@ -0,0 +1,90 @@
1
+ require 'right_publish/profile'
2
+
3
+ module RightPublish
4
+ class StorageManager
5
+ @@storage_type_regex = /\A(\w+)_storage/
6
+ @@storage_table = {}
7
+
8
+ def self.get_storage(type)
9
+ @@storage_table[type] if @@storage_table[type]
10
+ end
11
+
12
+ def self.storage_types()
13
+ type_hash = {}
14
+ @@storage_table.each_key { |k| type_hash[@@storage_type_regex.match(k.to_s)[1]] = k }
15
+ type_hash
16
+ end
17
+
18
+ def self.register_storage(module_type)
19
+ storage_key = module_type::STORAGE_KEY
20
+
21
+ if module_type.respond_to?(:get_directories) && @@storage_type_regex.match(storage_key.to_s)
22
+ @@storage_table[storage_key] = module_type
23
+ RightPublish::Profile.instance.register_section(@@storage_type_regex.match(storage_key.to_s)[1], module_type::STORAGE_OPTIONS)
24
+ else
25
+ raise TypeError
26
+ end
27
+ nil
28
+ end
29
+ end
30
+
31
+ module Storage
32
+ def self.sync_dirs(src, dest, options={})
33
+ src_dir = src.get_directories()
34
+ dest_dir = dest.get_directories()
35
+
36
+ src_map = get_etag_map(src_dir, options) {|file| file.etag}
37
+ dest_map = get_etag_map(dest_dir, options)
38
+
39
+ dest_map.each_pair do |hash, files|
40
+ files.each do |path, file|
41
+ unless src_map.include? hash and src_map[hash].include? path
42
+ Profile.log("Removing: #{path}", :debug)
43
+ file.destroy
44
+ end
45
+ end
46
+ end if options[:sweep]
47
+
48
+ src_map.each_pair do |hash, files|
49
+ # If we already have this data, just copy it, otherwise
50
+ # sync down a copy and make copies of that.
51
+ if dest_map.include? hash
52
+ local_copy = dest_map[hash].first.last
53
+ else
54
+ remote_file = files.shift
55
+ Profile.log("Synchronizing: #{remote_file.first}", :debug)
56
+ local_copy = dest_dir.files.create(:key=>remote_file.first, :body=>remote_file.last.body, :acl=>'public-read')
57
+ end
58
+
59
+ files.each_key do |file|
60
+ unless dest_map.include? hash and dest_map[hash].include? file
61
+ Profile.log("Duplicating: #{file}", :debug)
62
+ local_copy.copy(dest_dir.key, file, 'x-amz-acl'=>'public-read')
63
+ end
64
+ end
65
+ end
66
+ end
67
+
68
+ private
69
+
70
+ def self.get_etag_map(dir, options={})
71
+ hash_map = {}
72
+ dir.files.each do |file|
73
+ next if file.key[-1,1] == '/'
74
+ next if file.key[0,options[:subdir].size] != options[:subdir] if options.has_key? :subdir
75
+
76
+ md5 = dir.compute_md5(file)
77
+
78
+ if hash_map.has_key? md5
79
+ hash_map[md5][file.key] = file
80
+ else
81
+ hash_map[md5] = {file.key=>file}
82
+ end
83
+ end
84
+ hash_map
85
+ end
86
+ end
87
+ end
88
+
89
+ require 'right_publish/stores/local'
90
+ require 'right_publish/stores/s3'
@@ -0,0 +1,29 @@
1
+ require 'fog'
2
+ require 'digest/md5'
3
+
4
+ module RightPublish
5
+ module LocalStorage
6
+
7
+ DEFAULT_LOCAL_CACHEDIR = '~/.rp_cache'
8
+ STORAGE_KEY = :local_storage
9
+ STORAGE_OPTIONS = { :cache_dir=>DEFAULT_LOCAL_CACHEDIR}
10
+ def compute_md5(file)
11
+ Digest::MD5.file(File.join(service.path_to(self.key), file.key)).hexdigest
12
+ end
13
+
14
+ def self.get_directories()
15
+ Profile.log("Connecting to local cache.", :debug)
16
+ conn = Fog::Storage.new(
17
+ :provider=>"Local",
18
+ :local_root=>Profile.config[STORAGE_KEY][:cache_dir] )
19
+
20
+ Profile.log("Attaching to local cache: [#{Profile.config[STORAGE_KEY][:cache_dir]}].", :debug)
21
+ conn.directories.create(:key=>'.') unless File.exists? Profile.config[STORAGE_KEY][:cache_dir]
22
+ local_dir = conn.directories.get('.')
23
+ local_dir.extend(LocalStorage)
24
+ local_dir
25
+ end
26
+ end
27
+
28
+ RightPublish::StorageManager.register_storage(LocalStorage)
29
+ end
@@ -0,0 +1,35 @@
1
+ require 'fog'
2
+
3
+ module RightPublish
4
+ module S3Storage
5
+
6
+ STORAGE_KEY = :s3_storage
7
+ STORAGE_OPTIONS = {
8
+ :access_id => :attr_optional,
9
+ :access_key => :attr_optional,
10
+ :region => :attr_optional,
11
+ :remote_path => :attr_needed,
12
+ }
13
+
14
+ def compute_md5(file)
15
+ file.etag
16
+ end
17
+
18
+ def self.get_directories()
19
+ Profile.log("Connecting to S3.", :debug)
20
+ conn = Fog::Storage.new(
21
+ :provider => "AWS",
22
+ :aws_access_key_id => Profile.config[:remote_storage][:access_id],
23
+ :aws_secret_access_key => Profile.config[:remote_storage][:access_key],
24
+ :region => Profile.config[:remote_storage][:region]
25
+ )
26
+
27
+ Profile.log("Attaching to bucket: [#{Profile.config[:remote_storage][:remote_path]}].", :debug)
28
+ aws_bucket = conn.directories.get(Profile.config[:remote_storage][:remote_path])
29
+ aws_bucket.extend(S3Storage)
30
+ aws_bucket
31
+ end
32
+ end
33
+
34
+ RightPublish::StorageManager.register_storage(S3Storage)
35
+ end
@@ -0,0 +1,97 @@
1
+ #
2
+ # === Synopsis:
3
+ # RightScale RightPublish (right_publish) - (c) 2013 RightScale Inc
4
+ #
5
+
6
+ require 'trollop'
7
+
8
+ require 'right_publish/profile'
9
+ require 'right_publish/repo'
10
+
11
+ module RightPublish
12
+ module Main
13
+ REPOSITORY_TYPES = RepoManager.repo_types()
14
+ SUB_COMMANDS = %w(publish fetch store)
15
+
16
+ def self.run()
17
+ options = parse_args
18
+
19
+ begin
20
+ profile = RightPublish::Profile.instance
21
+ profile.load(options[:profile])
22
+ rescue LoadError => e
23
+ puts e
24
+ exit -1
25
+ end
26
+
27
+ # These options override profile attributes.
28
+ profile.settings[:local_storage][:cache_dir] = options[:local_cache] if options[:local_cache]
29
+ profile.settings[:verbose] = true if options[:verbose]
30
+
31
+ # We already know this is a valid type, parse_args checked
32
+ repo = RepoManager.get_repository(REPOSITORY_TYPES[options[:repo_type]])
33
+ begin
34
+ case options[:cmd]
35
+ when "publish"
36
+ repo.publish(options[:files], options[:dist])
37
+ when "store"
38
+ repo.store
39
+ when "fetch"
40
+ repo.fetch
41
+ else
42
+ end
43
+ rescue RuntimeError => e
44
+ RightPublish::Profile.log("Fatal Error:\n\t#{e}", :error)
45
+ exit -1
46
+ end
47
+ RightPublish::Profile.log("Success!")
48
+ end
49
+
50
+ def self.parse_args()
51
+ options = Trollop.options do
52
+ version "RightPublish alpha (c) 2013 RightScale Inc"
53
+ banner <<-EOS
54
+ RightPublish can manage a YUM/APT/RubyGem repository in remote storage, e.g. S3.
55
+
56
+ Usage:
57
+ right_publish [global_options] <command> [file1, file2, ...]
58
+
59
+ commands:
60
+ fetch
61
+ publish
62
+ store
63
+
64
+ global options:
65
+ EOS
66
+
67
+ opt :local_cache, "Local cache location", :type => String
68
+ opt :profile, "Publish profile", :type => String
69
+ opt :repo_type, "Repository type: #{REPOSITORY_TYPES.keys.inspect}", :type => String
70
+ 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
71
+ opt :verbose, "Verbose output"
72
+
73
+ stop_on SUB_COMMANDS
74
+ end
75
+
76
+ options[:cmd]= ARGV.shift
77
+ options[:files] = expand_argv_globs
78
+
79
+ Trollop.die "argument --profile is required" unless options[:profile]
80
+ Trollop.die "argument --repo-type is required" unless options[:repo_type]
81
+ Trollop.die "profile does not exist: #{options[:profile]}" unless File.exist?(options[:profile])
82
+ Trollop.die "invalid repository type: #{options[:repo_type]}" unless REPOSITORY_TYPES[options[:repo_type]]
83
+
84
+ options
85
+ end
86
+
87
+ def self.expand_argv_globs
88
+ files = []
89
+
90
+ ARGV.each do |glob|
91
+ files += Dir.glob(glob)
92
+ end
93
+
94
+ files
95
+ end
96
+ end
97
+ end