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.
@@ -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"])