rpmrepository 0.1.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.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: f0cdef908e95af3293de5d542c3eb9aa8c71a5c4
4
+ data.tar.gz: 6621f6ab0eebd01755f89fec3a352dca7e9832f3
5
+ SHA512:
6
+ metadata.gz: 7c3ea38b17f3952ae1ec2f5c5195e3c11ddaa7d41fb66c1bc727ecda338e8c82e721f424fa61bae4fefba2c4afb54c5e2e1ed585531419db070a6656b201f31b
7
+ data.tar.gz: 45e71797540b581ae48bfd34e7a3ae064e054af458bd73ac44b5e54b4aa0b5f2995657a7a3b844cf67d0dcd4fc770fbfe681ea8f2bc025b1a23f897bad273b67
@@ -0,0 +1,199 @@
1
+ #One package with possible multiple locations
2
+
3
+ module RPM
4
+
5
+ class Package < Monitor
6
+
7
+ attr_reader :uris
8
+ attr_reader :name
9
+ attr_reader :version
10
+ attr_reader :release
11
+ attr_reader :architecture
12
+ attr_reader :size
13
+ attr_reader :file_size
14
+ attr_reader :digests
15
+
16
+ def initialize raw_uri
17
+ super()
18
+ @uris = []
19
+ synchronize! {
20
+ raise ArgumentError, "Only String or URI, not #{raw_uri.class}" unless raw_uri.kind_of? String or raw_uri.kind_of? URI::Generic
21
+ if raw_uri.kind_of? URI::Generic
22
+ uri = raw_uri.dup
23
+ else
24
+ uri = URI::parse raw_uri
25
+ end
26
+ if (uri.scheme == 'file' or uri.scheme == nil) and File.exist? uri.path
27
+ 'file'
28
+ elsif uri.class == URI::HTTP and Net::HTTP.new(uri.host,uri.port).get(uri).is_a? Net::HTTPSuccess
29
+ 'remote'
30
+ else
31
+ raise ArgumentError, "Unreachable URI provided: #{uri.to_s}"
32
+ end
33
+ @uris.push uri
34
+ get_attributes
35
+ }
36
+ end
37
+
38
+ def duplicate_to dst_dir, file_name = get_default_name
39
+ if dst_dir[/^\..*/]
40
+ dst_dir = File::expand_path dst_dir
41
+ end
42
+ raise ArgumentError, "Path must be absolute (#{dst_dir})" unless dst_dir[/^\//]
43
+ target_uri = URI::parse "file:#{dst_dir}/#{file_name}"
44
+ synchronize! {
45
+ return true if get_local_uris.include? target_uri
46
+ if uri = get_local_uris.first and not uri.nil?
47
+ raise Errno::EEXIST, "File #{File::basename target_uri.path} already exist!" if File.exist? target_uri.path
48
+ begin
49
+ FileUtils::link uri.path, target_uri.path
50
+ rescue Errno::EXDEV => e
51
+ FileUtils::cp uri.path, target_uri.path
52
+ end
53
+ else
54
+ get_remote_uris.each { |remote_uri|
55
+ begin
56
+ raise Errno::EEXIST, "File #{File::basename target_uri.path} already exist!" if File.exist? target_uri.path
57
+ response = Net::HTTP.new(remote_uri.host,remote_uri.port).get(remote_uri)
58
+ if response.is_a? Net::HTTPSuccess
59
+ File.open(target_uri.path, "w+") { |f|
60
+ f.write response.body
61
+ }
62
+ else
63
+ next
64
+ end
65
+ #check what we write?
66
+ break
67
+ rescue Errno::ENETUNREACH, Errno::EHOSTUNREACH, Errno::ECONNREFUSED, URI::InvalidURIError => e
68
+ next
69
+ end
70
+ }
71
+ end
72
+ @uris.push target_uri if File.exist? target_uri.path
73
+ return File.exist? target_uri.path
74
+ }
75
+ end
76
+
77
+ #Removes all URL's undo provided dir
78
+ def deduplicate_undo dir
79
+ synchronize! {
80
+ (get_local_uris_undo dir).each { |uri|
81
+ FileUtils::rm_f uri.path
82
+ @uris.delete uri
83
+ }
84
+ }
85
+ end
86
+
87
+ #Try to expand current package by other source uris
88
+ def join! other_package
89
+ synchronize! {
90
+ if same_as? other_package
91
+ @uris += other_package.uris
92
+ @uris.uniq!
93
+ return true
94
+ else
95
+ return false
96
+ end
97
+ }
98
+ end
99
+
100
+ #Remove each reachable uri
101
+ def destroy!
102
+ synchronize! {
103
+ get_local_uris.each { |victim|
104
+ FileUtils::rm_f victim.path
105
+ @uris.delete victim
106
+ }
107
+ }
108
+ end
109
+
110
+ #Return true if packages has same attributes
111
+ def same_as? other
112
+ other.is_a? RPM::Package and
113
+ @name == other.name and
114
+ @version == other.version and
115
+ @release == other.release and
116
+ @architecture == other.architecture and
117
+ @digests[:sha256] == other.digests[:sha256]
118
+ end
119
+
120
+ #Return expected RPM Package file name
121
+ def get_default_name
122
+ "#{@name}-#{@version}-#{@release}.#{@architecture}.rpm"
123
+ end
124
+
125
+ #Return local URI's undo spicified directory
126
+ def get_local_uris_undo dir
127
+ if dir[/^\..*/]
128
+ dir = File::expand_path dir
129
+ end
130
+ unless dir[/.*\/$/]
131
+ dir = dir + '/'
132
+ end
133
+ synchronize! {
134
+ get_local_uris.select { |uri| uri.path[/^#{dir}/] }
135
+ }
136
+ end
137
+
138
+ #Return every local uris
139
+ def get_local_uris
140
+ synchronize! { @uris.select { |uri| uri.scheme == nil or uri.scheme == "file" } }
141
+ end
142
+
143
+ #Return every remote uris
144
+ def get_remote_uris
145
+ synchronize! { @uris.select { |uri| uri.scheme != nil and uri.scheme != "file" } }
146
+ end
147
+
148
+ #Try to repair package with broken local URIs
149
+ def repair!
150
+ synchronize {
151
+ @uris -= @uris.collect { |uri| (uri.scheme == 'file' or uri.scheme == nil) and not File.exist? uri.path }
152
+ if @uris.empty?
153
+ raise RuntimeError, 'Lack of URIs for curent package!'
154
+ else
155
+ return true
156
+ end
157
+ }
158
+ end
159
+
160
+ private
161
+ #Get RPM package attributes on package creation
162
+ def get_attributes
163
+ tmp_file_dir = '/tmp/' + SecureRandom.uuid
164
+ tmp_file_name = tmp_file_dir + '/package.rpm'
165
+ FileUtils.mkdir tmp_file_dir
166
+ raise RuntimeError, "Can't get package to determine attributes" unless duplicate_to tmp_file_dir, 'package.rpm'
167
+ @name, @version, @release, @architecture, @size = `rpm -q --queryformat '%{NAME} %{VERSION} %{RELEASE} %{ARCH} %{SIZE}' -p #{tmp_file_name} 2> /dev/null`.split ' '
168
+ if @name.nil? or @version.nil? or @release.nil? or @architecture.nil?
169
+ raise RuntimeError, "Can't parse name from #{tmp_file_name} by rpm -q command"
170
+ end
171
+ raise RuntimeError, "Unexpected file #{tmp_file_name} disappearing!" unless File.file? tmp_file_name
172
+ @file_size = File.size tmp_file_name
173
+ @digests = { :sha1 => Digest::SHA1.hexdigest(File.read tmp_file_name), :sha256 => Digest::SHA256.hexdigest(File.read tmp_file_name) }
174
+ ensure
175
+ deduplicate_undo tmp_file_dir
176
+ FileUtils.rm_rf tmp_file_dir
177
+ end
178
+
179
+ #Check that local URIs point at actually exist file
180
+ def check_uris
181
+ @uris.each { |uri|
182
+ if (uri.scheme == 'file' or uri.scheme == nil) and not File.exist? uri.path
183
+ #MESSAGE MATTERS!
184
+ raise RuntimeError, "File #{uri.path} not exist, but mentioned in package"
185
+ end
186
+ }
187
+ end
188
+
189
+ #Synchronize with useful check - enshure that every synced action preceeded with local uris check
190
+ def synchronize!
191
+ synchronize {
192
+ check_uris
193
+ yield if block_given?
194
+ }
195
+ end
196
+
197
+ end
198
+
199
+ end
@@ -0,0 +1,69 @@
1
+ #Attempt to sync concurrent managing of multiple repositories
2
+
3
+ module RPM
4
+
5
+ class RepoFactory
6
+
7
+ def initialize
8
+ @locker = Mutex.new
9
+ @locker.synchronize {
10
+ #Logging - now not needed
11
+ #@logger = Logging.logger['repo ' + @name]
12
+ #@logger.debug 'initializing'
13
+ @repositories = []
14
+ }
15
+ end
16
+
17
+ #Synced repository adding
18
+ def add_repository repository
19
+ @locker.synchronize {
20
+ if repository.kind_of? RPM::Repository
21
+ if (get_repository_by repository.name).is_a? RPM::Repository
22
+ raise ArgumentError, "Repository with such name already exist!"
23
+ end
24
+ @repositories.push repository
25
+ return true
26
+ else
27
+ return false
28
+ end
29
+ }
30
+ end
31
+
32
+ #Synced repository removal and destroying
33
+ def destroy_repository_by! name
34
+ repo = get_repository_by name
35
+ return false if repo.nil?
36
+ repo.destroy!
37
+ @locker.synchronize {
38
+ @repositories.delete repo
39
+ }
40
+ return true
41
+ end
42
+
43
+ #the way to non-block reading
44
+ def get_repositories
45
+ @repositories.clone
46
+ end
47
+
48
+ #Return repository by name
49
+ def get_repository_by name
50
+ unless name.kind_of? String
51
+ return nil
52
+ end
53
+ possible_repos = get_repositories.select { |repo| repo.name == name }
54
+ case possible_repos.count
55
+ when 1
56
+ return possible_repos.first
57
+ else
58
+ return nil
59
+ end
60
+ end
61
+
62
+ #wrap over Array's each
63
+ def each
64
+ get_repositories.each { |repository| yield repository if block_given? }
65
+ end
66
+
67
+ end
68
+
69
+ end
@@ -0,0 +1,63 @@
1
+ #Local RPM repository with packages
2
+
3
+ module RPM
4
+ class Repository < Monitor
5
+
6
+ require_relative 'repository_api'
7
+ require_relative 'repository_caching'
8
+ require_relative 'repository_metadata'
9
+
10
+ attr_reader :base_dir
11
+ attr_reader :status
12
+ attr_reader :tmp_dir
13
+ attr_accessor :name
14
+
15
+ @@statuses = {
16
+ :initializing => "Repository initialization in progress",
17
+ :rebuilding => "Rebuild repository metadata",
18
+ :destroying => "Repository destruction in progress",
19
+ :destroyed => "Repository removed and not available",
20
+ :ok => "Repository in normal state",
21
+ :cleaning => "Cleaning up temporary data"
22
+ }
23
+
24
+ #Init repository in specified directory
25
+ def initialize base_dir, name = SecureRandom.uuid, rebuild_args = ""
26
+ #Call Monitor's contructor to initialize it
27
+ super()
28
+ synchronize {
29
+ @status = :initializing
30
+ #Human-readable repo name (default here, but available from the outside)
31
+ @name = name
32
+ #Logging
33
+ @logger = Logging.logger['repo ' + @name]
34
+ @logger.debug 'initializing'
35
+ #Local basic directory
36
+ raise ArgumentError, "Base directory must be non-relative" if base_dir.nil? or not base_dir[/^\/.+/]
37
+ FileUtils::mkdir_p base_dir, { :mode => 0700 } unless Dir.exist? base_dir
38
+ @base_dir = Dir.new base_dir
39
+ File.chmod 0700, @base_dir.path
40
+ #Dir for some tmp files
41
+ Dir.mkdir @base_dir.path + "/tmp" unless Dir.exist? @base_dir.path + "/tmp"
42
+ @tmp_dir = Dir.new @base_dir.path + "/tmp"
43
+ #Init repository
44
+ FileUtils::mkdir "#{@base_dir.path}/Packages", { :mode => 0700 } unless @base_dir.entries.include? "Packages"
45
+ #Parts-relates initialization
46
+ initialization_metadata rebuild_args
47
+ initialization_cache
48
+ @logger.info "initialized"
49
+ #Making MD
50
+ rebuild
51
+ #Cache warming
52
+ validate_packages_cache
53
+ @status = :ok
54
+ }
55
+ end
56
+
57
+ #Include substructures
58
+ include RPM::Repository::Metadata
59
+ include RPM::Repository::Caching
60
+ include RPM::Repository::API
61
+
62
+ end
63
+ end
@@ -0,0 +1,152 @@
1
+ #Public user api of RPM::Repository
2
+ #Should'n changed frequently
3
+
4
+ module RPM::Repository::API
5
+
6
+ #Add single package to repository
7
+ def add_package! package
8
+ add_packages! [package]
9
+ end
10
+
11
+ #Add packages to repository
12
+ def add_packages! packages
13
+ @logger.info "adding packages"
14
+ duplicated_packages = []
15
+ rebuild_with {
16
+ packages.each { |package|
17
+ raise ArgumentError, "Package expected, but #{package.class}" unless package.is_a? RPM::Package
18
+ raise RuntimeError, "Package already exist!" if contains? package
19
+ package.duplicate_to "#{@base_dir.path}/Packages"
20
+ #hack that add package to cache. Escape package brain splitting (two packages with the same file)
21
+ if package.digests[get_checksum_type.to_sym]
22
+ @packages_cache[package.digests[get_checksum_type.to_sym]] = package
23
+ end
24
+ duplicated_packages.push package
25
+ }
26
+ }
27
+ rescue Exception => e
28
+ rebuild_with {
29
+ duplicated_packages.each { |package|
30
+ package.deduplicate_undo "#{@base_dir.path}/Packages"
31
+ }
32
+ }
33
+ raise e
34
+ end
35
+
36
+ #Remove package
37
+ def remove_package! package
38
+ @logger.info "removing package"
39
+ rebuild_with {
40
+ raise ArgumentError, "No such package in repository: #{package.get_default_name}" unless contains? package
41
+ get_own(package).deduplicate_undo @base_dir.path
42
+ }
43
+ end
44
+
45
+ #Remove packages
46
+ #Return two arrays: {:removed => [...], :skipped => [...]}
47
+ def remove_packages! packages
48
+ @logger.info "removing packages"
49
+ rezult = {:removed => [], :skipped => []}
50
+ rebuild_with {
51
+ packages.each { |package|
52
+ if contains? package
53
+ get_own(package).deduplicate_undo @base_dir.path
54
+ rezult[:removed].push package
55
+ else
56
+ rezult[:skipped].push package
57
+ @logger.warn "Package #{package} skipped: no such package in repository"
58
+ end
59
+ }
60
+ }
61
+ return rezult
62
+ end
63
+
64
+ #Remove packages by regex
65
+ #Return list with removed URL's
66
+ def parse_out_packages! name_expression
67
+ return remove_packages! get_packages_list_by(name_expression)
68
+ end
69
+
70
+ #Return packages array that match parameter
71
+ def get_packages_list_by name_expression
72
+ get_packages_list.select{ |package| package.get_default_name[name_expression] }
73
+ end
74
+
75
+ #Return true if repository contains package with same signature
76
+ def contains? alien_package
77
+ not get_packages_list.select{ |package| package.same_as? alien_package }.empty?
78
+ end
79
+ alias include? contains?
80
+
81
+ #Try to expand package URIs with own
82
+ #Return true if package expanded, false otherwise
83
+ def assimilate! alien_package
84
+ if contains? alien_package
85
+ alien_package.join! get_own(alien_package)
86
+ synchronize { @packages_cache[alien_package.digests[get_checksum_type.to_sym]] = alien_package }
87
+ return true
88
+ else
89
+ return false
90
+ end
91
+ end
92
+
93
+ #Remove repository files
94
+ def destroy!
95
+ @logger.info "destroying repo"
96
+ return @status if @status == :destroyed
97
+ synchronize {
98
+ @status = :destroying
99
+ clean
100
+ FileUtils::rm_rf @base_dir.path
101
+ @status = :destroyed
102
+ }
103
+ end
104
+
105
+ #Cleanup temporary files
106
+ def clean
107
+ @logger.info "cleaning repo"
108
+ synchronize {
109
+ @status = :cleaning
110
+ FileUtils::rm_rf @tmp_dir.path
111
+ FileUtils::mkdir @tmp_dir.path
112
+ @status = :ok
113
+ }
114
+ end
115
+
116
+ #Return template for YUM configuration
117
+ def get_local_conf
118
+ "
119
+ [#{@name}]
120
+ name=#{@name}
121
+ baseurl=file:///#{@base_dir.path}
122
+ gpgcheck=0
123
+ enabled=0
124
+ "
125
+ end
126
+
127
+ #Return array of packages
128
+ def get_packages_list
129
+ synchronize { validate_packages_cache && get_packages_from_cache }
130
+ end
131
+
132
+ #Public and safe realization of rebuilding - just rebuild repository
133
+ def rebuild
134
+ rebuild_with
135
+ end
136
+ private
137
+
138
+ #Return Package from current repository
139
+ #Method not public because it brings packages split-brain (two same package into runtime)
140
+ def get_own package
141
+ same_packages = get_packages_list.select { |repo_package| repo_package.same_as? package }
142
+ case same_packages.count
143
+ when 1
144
+ return same_packages.first
145
+ when 0
146
+ return nil
147
+ else
148
+ raise RuntimeError, "More than one #{package.get_default_name} package in repository"
149
+ end
150
+ end
151
+
152
+ end
@@ -0,0 +1,81 @@
1
+ #RPM Repository Caching module
2
+ #Mix for RPM::Repository to realize MD in-memory caching
3
+
4
+ module RPM::Repository::Caching
5
+ private
6
+
7
+ #Return values from cached packages list - UNSYNC!!!
8
+ def get_packages_from_cache
9
+ @packages_cache.values
10
+ end
11
+
12
+ #Check that current package list cache valid. if not - validate
13
+ def validate_packages_cache
14
+ @logger.debug "validating packages cache"
15
+ synchronize {
16
+ if repomd_cache_hit?
17
+ @logger.debug "MD cache hit"
18
+ return true
19
+ else
20
+ refresh_md_files_cache
21
+ refresh_packages_cache
22
+ return true
23
+ end
24
+ }
25
+ end
26
+
27
+ #All below used just once. In case of multithreading usage - sync it by synchronize { body }
28
+
29
+ #true means 100% hit in cached repository state - UNSYNC!!! - used once
30
+ def repomd_cache_hit?
31
+ if @repomd_cache.is_a? REXML::Document
32
+ return read_repomd_doc.elements["/repomd/revision"].text == @repomd_cache.elements["/repomd/revision"].text
33
+ else
34
+ return false
35
+ end
36
+ end
37
+
38
+ #refreshing each md file cach
39
+ def refresh_md_files_cache
40
+ @repomd_cache = read_repomd_doc
41
+ @md_content_cache.keys.each { |type|
42
+ @md_content_cache[type] = read_md_doc type.to_s
43
+ }
44
+ end
45
+
46
+ #Actually check packages cache: create new one and fill it with old cache and newly created one
47
+ def refresh_packages_cache
48
+ #save current cache state
49
+ prev_cache = @packages_cache
50
+ #clean current cache - used by other functions to refresh cache
51
+ @packages_cache = {}
52
+ @md_content_cache[:primary].each_element('/metadata/package') { |package_md|
53
+ chksum = package_md.elements['checksum'].text
54
+ if prev_cache[chksum]
55
+ #get package from previous cache
56
+ @packages_cache[chksum] = prev_cache[chksum]
57
+ else
58
+ #construct new one
59
+ url = ''
60
+ if package_md.elements['location'].attributes["xml:base"]
61
+ url = URI::parse (package_md.elements['location'].attributes["xml:base"]+'/'+package_md.elements['location'].attributes["href"])
62
+ else
63
+ url = URI::parse ('file://' + @base_dir.path+'/'+package_md.elements['location'].attributes["href"])
64
+ end
65
+ @packages_cache[chksum] = RPM::Package.new url
66
+ end
67
+ }
68
+ @logger.debug 'package cache refreshed'
69
+ end
70
+
71
+ #Cache-related part of initialization
72
+ def initialization_cache
73
+ #cached and parsed main XML MD file - repomd.xml
74
+ @repomd_cache = nil
75
+ #cached and parsed other XML documents
76
+ @md_content_cache = { :primary => nil }
77
+ #Repository Packages cache. Format: checksum => RPM::Package
78
+ @packages_cache = {}
79
+ end
80
+
81
+ end
@@ -0,0 +1,72 @@
1
+ #RPM Repository Metadata module
2
+ #Mix for RPM::Repository to work witn MD
3
+
4
+ module RPM::Repository::Metadata
5
+ private
6
+
7
+ #Rebuild repository with current configuration with additional args and block - synced
8
+ def rebuild_with args = ""
9
+ synchronize {
10
+ yield if block_given?
11
+ @status = :rebuilding
12
+ #TODO: add group file to rebuilding
13
+ unless system "createrepo -v --profile --update #{@base_dir.path} -s #{get_checksum_type} #{@extended_rebuild_args} #{args} &> '#{@tmp_dir.path}/rebuild-#{Time.now.to_s}'"
14
+ raise RuntimeError, "Can't rebuild repository #{@name}"
15
+ end
16
+ @status = :ok
17
+ }
18
+ @logger.info "rebuilded"
19
+ return true
20
+ end
21
+
22
+ #read and parse repomd.xml - UNSYNC! - low level operation - no reason to make locks
23
+ def read_repomd_doc
24
+ REXML::Document.new File.open(@base_dir.path + "/repodata/repomd.xml")
25
+ end
26
+
27
+ #return md file document by type - UNSYNC!
28
+ #assume that @repomd_cache is valid
29
+ def read_md_doc type
30
+ @logger.debug "getting #{type} md file"
31
+ if @repomd_cache.elements["/repomd/data[@type=\"#{type}\"]/location"].attributes["href"]
32
+ path_to_md_file = @base_dir.path+'/'+@repomd_cache.elements["/repomd/data[@type=\"#{type}\"]/location"].attributes["href"]
33
+ #raw_md_file = File.read path_to_md_file
34
+ case path_to_md_file
35
+ when /\.gz$/
36
+ Zlib::GzipReader.open(path_to_md_file) { |gz|
37
+ return REXML::Document.new gz.read
38
+ }
39
+ when /\.xml$/
40
+ return REXML::Document.new File.read path_to_md_file
41
+ else
42
+ raise RuntimeError, "Can't determine type of #{path_to_md_file}"
43
+ end
44
+ else
45
+ raise ArgumentError, 'No #{type} md file record in repomd.xml'
46
+ end
47
+ end
48
+
49
+ #Get checksum type from repomd.xml to be used for correct rebuilding - UNSYNC!!!
50
+ def get_checksum_type
51
+ #@logger.debug "getting checksum type"
52
+ if File::exist? (@base_dir.path + "/repodata/repomd.xml")
53
+ repomd_doc = read_repomd_doc
54
+ if repomd_doc.elements["/repomd/data/checksum"].attributes["type"]
55
+ return repomd_doc.elements["/repomd/data/checksum"].attributes["type"]
56
+ else
57
+ #if no such field (improbable)
58
+ return "sha256"
59
+ end
60
+ else
61
+ #if this is fresh repository
62
+ return "sha256"
63
+ end
64
+ end
65
+
66
+ #MD-related initialization part
67
+ def initialization_metadata rebuild_args
68
+ #Default additional arguments for createrepo
69
+ @extended_rebuild_args = rebuild_args
70
+ end
71
+
72
+ end
@@ -0,0 +1,16 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ module RPM
4
+
5
+ require 'fileutils'
6
+ require 'securerandom'
7
+ require 'uri'
8
+ require 'net/http'
9
+ require 'rexml/document'
10
+ require 'logging'
11
+
12
+ require_relative 'package'
13
+ require_relative 'repository'
14
+ require_relative 'repofactory'
15
+
16
+ end
metadata ADDED
@@ -0,0 +1,64 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rpmrepository
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - nothing
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2017-09-15 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: logging
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ description: Gem to create/manage RPM repository(ies)/package(s)
28
+ email:
29
+ executables: []
30
+ extensions: []
31
+ extra_rdoc_files: []
32
+ files:
33
+ - lib/package.rb
34
+ - lib/repofactory.rb
35
+ - lib/repository.rb
36
+ - lib/repository_api.rb
37
+ - lib/repository_caching.rb
38
+ - lib/repository_metadata.rb
39
+ - lib/rpm.rb
40
+ homepage:
41
+ licenses:
42
+ - Beerware
43
+ metadata: {}
44
+ post_install_message:
45
+ rdoc_options: []
46
+ require_paths:
47
+ - lib
48
+ required_ruby_version: !ruby/object:Gem::Requirement
49
+ requirements:
50
+ - - ">="
51
+ - !ruby/object:Gem::Version
52
+ version: '0'
53
+ required_rubygems_version: !ruby/object:Gem::Requirement
54
+ requirements:
55
+ - - ">="
56
+ - !ruby/object:Gem::Version
57
+ version: '0'
58
+ requirements: []
59
+ rubyforge_project:
60
+ rubygems_version: 2.6.11
61
+ signing_key:
62
+ specification_version: 4
63
+ summary: Deal with RPM Repository/Package
64
+ test_files: []