rpmrepository 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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: []