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.
@@ -1,10 +1,8 @@
1
1
  module RightPublish
2
- class AptRepo
3
- include RightPublish::Repo
4
-
2
+ class AptRepo < Repo
5
3
  DEFAULT_APT_AUTO = true
6
4
  DEFAULT_APT_DIR = 'apt/'
7
- DEFAULT_DESCRIPTION = "RightScale RightLink Repository"
5
+ DEFAULT_DESCRIPTION = "Apt Repository"
8
6
  REPO_KEY = :apt_repo
9
7
 
10
8
  REPO_OPTIONS = {
@@ -22,7 +20,7 @@ module RightPublish
22
20
  @@supported_archs = [ 'i386', 'amd64' ]
23
21
  @@all_archs = [ 'i386', 'amd64', BIN_ALL_ARCH ]
24
22
 
25
- def publish(file_or_dir, target)
23
+ def add(file_or_dir, target)
26
24
  repo_updates = {}
27
25
 
28
26
  # If we specified a target, let's make sure it's in our profile
@@ -39,9 +37,6 @@ module RightPublish
39
37
  pkgs << path
40
38
  end
41
39
 
42
- # Synchronize the upstream source to our local cache
43
- fetch
44
-
45
40
  repo_path = File.join(Profile.config[:local_storage][:cache_dir], repo_config[:subdir])
46
41
  write_conf_file repo_path if repo_config[:auto]
47
42
 
@@ -56,9 +51,12 @@ module RightPublish
56
51
  end
57
52
  end
58
53
  rebuild_index(repo_config[:subdir]) if not repo_config[:auto]
54
+ end
59
55
 
60
- # Commit back to remote storage
61
- store
56
+ def annotate(options={})
57
+ options[:subdir] ||= File.join(repo_config[:subdir], 'pool', 'main')
58
+ options[:filter] = ['*.deb']
59
+ super(options)
62
60
  end
63
61
 
64
62
  def self.pkg_parts(pkg_name)
@@ -1,24 +1,23 @@
1
1
  module RightPublish
2
- class GemRepo
3
- include RightPublish::Repo
4
-
2
+ class GemRepo < Repo
5
3
  DEFAULT_GEM_DIR = 'gems/'
6
4
  REPO_KEY = :gem_repo
7
- REPO_OPTIONS = {:subdir=>DEFAULT_GEM_DIR}
5
+ REPO_OPTIONS = {:subdir=>DEFAULT_GEM_DIR, :prune=>false}
8
6
 
9
7
  GEMS_SUBDIRECTORY = 'gems'
10
8
  GEM_EXT = 'gem'
11
9
 
12
- def publish(file_or_dir, target)
10
+ def add(file_or_dir, target)
13
11
  gems_list = get_pkg_list(file_or_dir, GEM_EXT)
14
12
 
15
- # Synchronize the upstream source to our local cache
16
- fetch
17
-
18
13
  # Copy gem files to the repository
19
14
  destination = File.join(repo_config[:subdir], GEMS_SUBDIRECTORY)
20
15
  gems_list.each do |path|
21
- do_in_subdir(destination) { prune_all("#{pkg_parts(path)[:name]}-*.gem") }
16
+ # RubyGems repos do not normally prune; it must be explicitly enabled.
17
+ if repo_config[:prune]
18
+ do_in_subdir(destination) { prune_all("#{pkg_parts(path)[:name]}-*.gem") }
19
+ end
20
+
22
21
  install_file(path, destination)
23
22
  end
24
23
 
@@ -28,11 +27,16 @@ module RightPublish
28
27
  indexed = system('gem generate_index 2>&1 >/dev/null') # requires 'builder' gem to be installed
29
28
  raise Exception, "gem generate_index failed; cannot continue publishing" unless indexed
30
29
  end
30
+ end
31
31
 
32
- # Commit back to remote storage
33
- store
32
+ def annotate(options={})
33
+ options[:subdir] ||= File.join(repo_config[:subdir], 'gems')
34
+ options[:filter] = ['*.gem']
35
+ super(options)
34
36
  end
35
37
 
38
+ protected
39
+
36
40
  def pkg_parts(name)
37
41
  result = {:name=>nil, :version=>nil}
38
42
  result[:name], result[:version] = /([A-Za-z0-9\-_]+)-([0-9\.]+)\.#{GEM_EXT}\Z/.match(name).captures
@@ -0,0 +1,45 @@
1
+ module RightPublish
2
+ class MsiRepo < Repo
3
+ DEFAULT_MSI_DIR = 'msi/'
4
+ REPO_KEY = :msi_repo
5
+ REPO_OPTIONS = {:subdir=>DEFAULT_MSI_DIR, :prune=>true}
6
+
7
+ GEMS_SUBDIRECTORY = 'gems'
8
+ MSI_EXT = 'msi'
9
+
10
+ def add(file_or_dir, target)
11
+ msi_list = get_pkg_list(file_or_dir, MSI_EXT)
12
+
13
+ # Copy MSI files to the repository
14
+ destination = repo_config[:subdir]
15
+ msi_list.each do |path|
16
+ # RubyGems repos do not normally prune; it must be explicitly enabled.
17
+ if repo_config[:prune]
18
+ do_in_subdir(destination) { prune_all("#{pkg_parts(path)[:name]}-*.msi") }
19
+ end
20
+
21
+ install_file(path, destination)
22
+ end
23
+
24
+ # No index generation is necessary; MSI is not a distribution mechanism. The annotated
25
+ # HTML will suffice to direct people to the proper download.
26
+ end
27
+
28
+ def annotate(options={})
29
+ options[:subdir] ||= repo_config[:subdir]
30
+ options[:filter] = ['*.msi']
31
+ options[:use_remote_storage] = true
32
+ super(options)
33
+ end
34
+
35
+ protected
36
+
37
+ def pkg_parts(name)
38
+ result = {:name=>nil, :version=>nil}
39
+ result[:name], result[:version] = /([A-Za-z0-9\-_]+)-([0-9\.]+)\.#{MSI_EXT}\Z/.match(name).captures
40
+ result
41
+ end
42
+ end
43
+
44
+ RightPublish::RepoManager.register_repo(MsiRepo)
45
+ end
@@ -1,13 +1,10 @@
1
1
 
2
2
 
3
3
  module RightPublish
4
-
5
- class YumRepo
6
- include RightPublish::Repo
7
-
4
+ class YumRepo < Repo
8
5
  DEFAULT_YUM_EPEL = 1
9
6
  DEFAULT_YUM_DIR = 'yum/'
10
- DEFAULT_DESCRIPTION = "RightScale RightLink Repository"
7
+ DEFAULT_DESCRIPTION = "Yum Repository"
11
8
  REPO_KEY = :yum_repo
12
9
  DEFAULT_EPEL_LAYOUT = "rightlink"
13
10
  REPO_OPTIONS = {
@@ -27,7 +24,7 @@ module RightPublish
27
24
  ARCHITECTURES = [ 'i386', 'x86_64' ]
28
25
  PKG_TYPES = [ ARCHITECTURES, BIN_ALL_ARCH, SRC_ALL_ARCH ]
29
26
 
30
- def publish(file_or_dir, target)
27
+ def add(file_or_dir, target)
31
28
  repo_updates = {}
32
29
 
33
30
  get_pkg_list(file_or_dir, YUM_EXT) do |path|
@@ -40,9 +37,6 @@ module RightPublish
40
37
  end
41
38
  return if repo_updates.size == 0
42
39
 
43
- # Synchronize the upstream source to our local cache
44
- fetch
45
-
46
40
  full_pkg_paths = []
47
41
  repo_updates.each_pair do |repo_path, pkgs|
48
42
  pkgs.each do |pkg|
@@ -57,9 +51,12 @@ module RightPublish
57
51
  regen_metadata( repo_path )
58
52
  sign_metadata( repo_path )
59
53
  end
54
+ end
60
55
 
61
- # Commit back to remote storage
62
- store
56
+ def annotate(options={})
57
+ options[:subdir] ||= File.join(repo_config[:subdir], '1')
58
+ options[:filter] = ['*.rpm']
59
+ super(options)
63
60
  end
64
61
 
65
62
  def import_pkg( pkg, repo_path )
@@ -29,11 +29,23 @@ module RightPublish
29
29
  end
30
30
 
31
31
  module Storage
32
+ def self.ls(bucket, options={})
33
+ storage = bucket.get_directories()
34
+ storage.files.each do |file|
35
+ # skip directories
36
+ next if file.key[-1,1] == '/'
37
+ # skip files not located in specified subdir
38
+ next if file.key[0,options[:subdir].size] != options[:subdir] if options.has_key? :subdir
39
+
40
+ yield(file)
41
+ end
42
+ end
43
+
32
44
  def self.sync_dirs(src, dest, options={})
33
45
  src_dir = src.get_directories()
34
46
  dest_dir = dest.get_directories()
35
47
 
36
- src_map = get_etag_map(src_dir, options) {|file| file.etag}
48
+ src_map = get_etag_map(src_dir, options)
37
49
  dest_map = get_etag_map(dest_dir, options)
38
50
 
39
51
  dest_map.each_pair do |hash, files|
@@ -53,7 +65,9 @@ module RightPublish
53
65
  else
54
66
  remote_file = files.shift
55
67
  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')
68
+ local_copy = dest_dir.files.create(:key=>remote_file.first, :body=>remote_file.last.body,
69
+ :acl=>'public-read',
70
+ :content_type => guess_mime_type(remote_file.first))
57
71
  end
58
72
 
59
73
  files.each_key do |file|
@@ -65,12 +79,37 @@ module RightPublish
65
79
  end
66
80
  end
67
81
 
68
- private
82
+ # Given a filename, make an educated guess as to its MIME type. We rely on the mime-types
83
+ # gem and its built-in database of extensions and types, but many extensions are in use
84
+ # by multiple MIME types.
85
+ #
86
+ # If the extension maps to more than one MIME type, we will pick the first type that is
87
+ # a member of RightPublish::MIME_TYPES. This gives us 100% reliable behavior for MIME
88
+ # types that RightPublish "knows," and passable behavior for other types.
89
+ #
90
+ # @param [String] filename relative or absolute filename
91
+ # @return [String] the inferred MIME type for filename, based on its extension
92
+ def self.guess_mime_type(filename)
93
+ filename = File.basename(filename)
94
+ candidates = MIME::Types.type_for(filename)
95
+
96
+ if candidates.size == 1
97
+ winner = candidates.first
98
+ elsif preferred = candidates.detect { |c| RightPublish::MIME_TYPES.keys.include? c.to_s }
99
+ winner = preferred
100
+ else
101
+ winner = 'application/octet-stream'
102
+ end
103
+
104
+ winner.to_s
105
+ end
69
106
 
70
107
  def self.get_etag_map(dir, options={})
71
108
  hash_map = {}
72
109
  dir.files.each do |file|
110
+ # skip directories
73
111
  next if file.key[-1,1] == '/'
112
+ # skip files not located in specified subdir
74
113
  next if file.key[0,options[:subdir].size] != options[:subdir] if options.has_key? :subdir
75
114
 
76
115
  md5 = dir.compute_md5(file)
@@ -3,10 +3,10 @@ require 'digest/md5'
3
3
 
4
4
  module RightPublish
5
5
  module LocalStorage
6
-
7
- DEFAULT_LOCAL_CACHEDIR = '~/.rp_cache'
6
+ DEFAULT_LOCAL_CACHEDIR = File.expand_path('~/.right_publish/cache')
8
7
  STORAGE_KEY = :local_storage
9
- STORAGE_OPTIONS = { :cache_dir=>DEFAULT_LOCAL_CACHEDIR}
8
+ STORAGE_OPTIONS = { :cache_dir=>DEFAULT_LOCAL_CACHEDIR }
9
+
10
10
  def compute_md5(file)
11
11
  Digest::MD5.file(File.join(service.path_to(self.key), file.key)).hexdigest
12
12
  end
data/lib/right_publish.rb CHANGED
@@ -3,95 +3,33 @@
3
3
  # RightScale RightPublish (right_publish) - (c) 2013 RightScale Inc
4
4
  #
5
5
 
6
- require 'trollop'
7
-
8
- require 'right_publish/profile'
9
- require 'right_publish/repo'
6
+ require 'mime/types'
10
7
 
11
8
  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
9
+ # Combined list of MIME types that should be registered with mime-types, AND preferred
10
+ # MIME types for this application to use, in cases where a given filename extension is
11
+ # used by multiple MIME types.
12
+ MIME_TYPES = {
13
+ 'application/x-deb' => 'deb',
14
+ 'application/x-ruby-gem' => 'gem',
15
+ 'application/x-rpm' => 'rpm',
16
+ 'application/x-msi' => 'msi',
17
+ 'text/xml' => 'xml',
18
+ }
19
+ end
93
20
 
94
- files
21
+ # Register some extra MIME types that the gem may not know about.
22
+ RightPublish::MIME_TYPES.each_pair do |name, extension|
23
+ if MIME::Types[name].empty?
24
+ type = MIME::Type.new(name) do |t|
25
+ t.extensions = [extension]
26
+ t.encoding = '8bit'
95
27
  end
28
+ MIME::Types.add(type)
96
29
  end
97
30
  end
31
+
32
+ require 'right_publish/profile'
33
+ require 'right_publish/annotation'
34
+ require 'right_publish/repo'
35
+ require 'right_publish/cli'
@@ -5,11 +5,11 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = "right_publish"
8
- s.version = "0.3.0"
8
+ s.version = "0.4.0"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["Brian Szmyd", "Tony Spataro"]
12
- s.date = "2013-08-12"
12
+ s.date = "2013-11-01"
13
13
  s.description = "A tool for maintaining S3-based DEB, GEM and RPM packages."
14
14
  s.email = "support@rightscale.com"
15
15
  s.executables = ["autosign.expect", "right_publish"]
@@ -27,16 +27,20 @@ Gem::Specification.new do |s|
27
27
  "bin/autosign.expect",
28
28
  "bin/right_publish",
29
29
  "lib/right_publish.rb",
30
+ "lib/right_publish/annotation.rb",
31
+ "lib/right_publish/cli.rb",
30
32
  "lib/right_publish/profile.rb",
31
33
  "lib/right_publish/repo.rb",
32
34
  "lib/right_publish/repos/apt.rb",
33
35
  "lib/right_publish/repos/gem.rb",
36
+ "lib/right_publish/repos/msi.rb",
34
37
  "lib/right_publish/repos/yum.rb",
35
38
  "lib/right_publish/repos/zypp.rb",
36
39
  "lib/right_publish/storage.rb",
37
40
  "lib/right_publish/stores/local.rb",
38
41
  "lib/right_publish/stores/s3.rb",
39
42
  "right_publish.gemspec",
43
+ "spec/annotation_spec.rb",
40
44
  "spec/repo_manager_spec.rb",
41
45
  "spec/repo_spec.rb",
42
46
  "spec/repos/apt_spec.rb",
@@ -51,7 +55,7 @@ Gem::Specification.new do |s|
51
55
  s.homepage = "https://github.com/rightscale/right_publish"
52
56
  s.licenses = ["Proprietary"]
53
57
  s.require_paths = ["lib"]
54
- s.rubygems_version = "1.8.24"
58
+ s.rubygems_version = "1.8.23"
55
59
  s.summary = "Package publishing and indexing tool"
56
60
 
57
61
  if s.respond_to? :specification_version then
@@ -60,6 +64,8 @@ Gem::Specification.new do |s|
60
64
  if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
61
65
  s.add_runtime_dependency(%q<builder>, [">= 0"])
62
66
  s.add_runtime_dependency(%q<fog>, ["~> 1.9"])
67
+ s.add_runtime_dependency(%q<excon>, ["<= 0.25.3"])
68
+ s.add_runtime_dependency(%q<mime-types>, ["~> 1.0"])
63
69
  s.add_runtime_dependency(%q<trollop>, ["~> 2.0"])
64
70
  s.add_development_dependency(%q<rake>, ["~> 0.9"])
65
71
  s.add_development_dependency(%q<jeweler>, ["~> 1.8.3"])
@@ -73,6 +79,8 @@ Gem::Specification.new do |s|
73
79
  else
74
80
  s.add_dependency(%q<builder>, [">= 0"])
75
81
  s.add_dependency(%q<fog>, ["~> 1.9"])
82
+ s.add_dependency(%q<excon>, ["<= 0.25.3"])
83
+ s.add_dependency(%q<mime-types>, ["~> 1.0"])
76
84
  s.add_dependency(%q<trollop>, ["~> 2.0"])
77
85
  s.add_dependency(%q<rake>, ["~> 0.9"])
78
86
  s.add_dependency(%q<jeweler>, ["~> 1.8.3"])
@@ -87,6 +95,8 @@ Gem::Specification.new do |s|
87
95
  else
88
96
  s.add_dependency(%q<builder>, [">= 0"])
89
97
  s.add_dependency(%q<fog>, ["~> 1.9"])
98
+ s.add_dependency(%q<excon>, ["<= 0.25.3"])
99
+ s.add_dependency(%q<mime-types>, ["~> 1.0"])
90
100
  s.add_dependency(%q<trollop>, ["~> 2.0"])
91
101
  s.add_dependency(%q<rake>, ["~> 0.9"])
92
102
  s.add_dependency(%q<jeweler>, ["~> 1.8.3"])