repomate 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.
- data/bin/repomate +65 -0
- data/etc/config.yml +18 -0
- data/lib/repomate/architecture.rb +105 -0
- data/lib/repomate/base.rb +335 -0
- data/lib/repomate/category.rb +60 -0
- data/lib/repomate/cli.rb +123 -0
- data/lib/repomate/component.rb +102 -0
- data/lib/repomate/configuration.rb +75 -0
- data/lib/repomate/database.rb +30 -0
- data/lib/repomate/metafile.rb +137 -0
- data/lib/repomate/package.rb +116 -0
- data/lib/repomate/repository.rb +49 -0
- data/lib/repomate/suite.rb +96 -0
- data/lib/repomate/templates/archrelease.erb +6 -0
- data/lib/repomate/templates/packages.erb +6 -0
- data/lib/repomate/templates/suiterelease.erb +41 -0
- data/lib/repomate.rb +14 -0
- metadata +152 -0
data/bin/repomate
ADDED
@@ -0,0 +1,65 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
# expand path if in development mode
|
4
|
+
if File.exists?(File.join(File.join(File.dirname(__FILE__), '..', '.git')))
|
5
|
+
$: << File.join(File.dirname(__FILE__), '..', 'lib')
|
6
|
+
end
|
7
|
+
|
8
|
+
require 'repomate'
|
9
|
+
require 'rubygems'
|
10
|
+
require 'slop'
|
11
|
+
|
12
|
+
options = Slop.parse do
|
13
|
+
banner "RepoMate (A simple debian repository management tool)
|
14
|
+
|
15
|
+
Usage: #{$0} add -s squeeze [-c main] <package>
|
16
|
+
#{$0} publish
|
17
|
+
#{$0} listpackages -r production
|
18
|
+
|
19
|
+
Actions:
|
20
|
+
add - Add a package to the staging area
|
21
|
+
publish - Move packages from staging area to production
|
22
|
+
save - Save a checkpoint
|
23
|
+
load - Load a checkpoint
|
24
|
+
listpackages - List packages
|
25
|
+
setup - Setup the pool
|
26
|
+
|
27
|
+
Options:"
|
28
|
+
on :s, :suitename=, "Set the name of the suite (lenny/squeeze...)", :argument => true
|
29
|
+
on :c, :component=, "Set the name of the component (main/contrib...)", :default => "main"
|
30
|
+
on :a, :architecture=, "Set the name of the component (main/contrib...)", :argument => true
|
31
|
+
on :r, :repodir, "Type of pool/category (stage/pool/production)", :argument => true
|
32
|
+
on :force, "Force action", :default => false
|
33
|
+
on :h, :help, 'Print this help message', :tail => true do
|
34
|
+
puts help
|
35
|
+
exit
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
cli = RepoMate::Cli.new
|
40
|
+
|
41
|
+
if ARGV.include?("add")
|
42
|
+
filename = nil
|
43
|
+
ARGV.each do |arg|
|
44
|
+
if arg =~ /\.deb/
|
45
|
+
filename = arg
|
46
|
+
end
|
47
|
+
end
|
48
|
+
if filename && File.exists?(filename)
|
49
|
+
cli.stage(options, filename)
|
50
|
+
else
|
51
|
+
STDERR.puts "File does not exist"
|
52
|
+
end
|
53
|
+
elsif ARGV.include?("publish")
|
54
|
+
cli.publish(options)
|
55
|
+
elsif ARGV.include?("save")
|
56
|
+
cli.save_checkpoint
|
57
|
+
elsif ARGV.include?("load")
|
58
|
+
cli.choose_checkpoint
|
59
|
+
elsif ARGV.include?("listpackages")
|
60
|
+
cli.list_packagelist(options)
|
61
|
+
elsif ARGV.include?("setup")
|
62
|
+
cli.setup(options)
|
63
|
+
else
|
64
|
+
puts options.help
|
65
|
+
end
|
data/etc/config.yml
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
---
|
2
|
+
:rootdir: /var/lib/repomate/repository
|
3
|
+
:logdir: /var/log/repomate
|
4
|
+
:dpkg: /usr/bin/dpkg
|
5
|
+
:suites:
|
6
|
+
- lenny
|
7
|
+
- squeeze
|
8
|
+
:components:
|
9
|
+
- main
|
10
|
+
- contrib
|
11
|
+
:architectures:
|
12
|
+
- all
|
13
|
+
- amd64
|
14
|
+
:origin: Repository
|
15
|
+
:label: Repository
|
16
|
+
:gpg_enable: yes
|
17
|
+
:gpg_email: someone@example.net
|
18
|
+
:gpg_password: secret
|
@@ -0,0 +1,105 @@
|
|
1
|
+
# RepoMate module
|
2
|
+
module RepoMate
|
3
|
+
|
4
|
+
# Class for the architecture layer of the directory structure
|
5
|
+
class Architecture
|
6
|
+
|
7
|
+
# Init
|
8
|
+
def initialize(architecture, component, suitename, category)
|
9
|
+
@architecture = architecture
|
10
|
+
@component = component
|
11
|
+
@suitename = suitename
|
12
|
+
@category = category
|
13
|
+
end
|
14
|
+
|
15
|
+
# Returns the given architecture name (eg. all, amd64)
|
16
|
+
def name
|
17
|
+
@architecture
|
18
|
+
end
|
19
|
+
|
20
|
+
# Returns the directory strcuture of the architecture including all lower layers
|
21
|
+
def directory
|
22
|
+
File.join(Cfg.rootdir, @category, @suitename, @component, "binary-#{name}")
|
23
|
+
end
|
24
|
+
|
25
|
+
# Checks if the architecture directory exists
|
26
|
+
def exist?
|
27
|
+
Dir.exist?(directory)
|
28
|
+
end
|
29
|
+
|
30
|
+
# Checks if the architecture is allowed (See: configurationfile)
|
31
|
+
def is_allowed?
|
32
|
+
self.allowed.include?(@architecture)
|
33
|
+
end
|
34
|
+
|
35
|
+
# Checks if directory is unused
|
36
|
+
def is_unused?(dir)
|
37
|
+
status = true
|
38
|
+
|
39
|
+
path = Dir.glob(File.join(dir, "*"))
|
40
|
+
path.each do |dirorfile|
|
41
|
+
status = false if File.directory?(dirorfile)
|
42
|
+
status = false if File.basename(dirorfile) =~ /\.deb$/
|
43
|
+
end
|
44
|
+
|
45
|
+
status
|
46
|
+
end
|
47
|
+
|
48
|
+
# Creates the directory strcuture of the architecture including all lower layers
|
49
|
+
def create
|
50
|
+
FileUtils.mkdir_p(directory) unless exist?
|
51
|
+
end
|
52
|
+
|
53
|
+
# Deletes the architecture directory including all lower layers
|
54
|
+
def destroy
|
55
|
+
FileUtils.rm_r(directory) if exist?
|
56
|
+
end
|
57
|
+
|
58
|
+
# Returns a list of all debian files in the architecture directory
|
59
|
+
def files
|
60
|
+
Dir.glob(File.join(directory, "*.deb"))
|
61
|
+
end
|
62
|
+
|
63
|
+
# Returns a dataset including the name of the architecture and the fullpath recursive through all lower layers
|
64
|
+
def self.dataset(category=nil)
|
65
|
+
data = []
|
66
|
+
self.all.each do |entry|
|
67
|
+
parts = entry.split(/\//)
|
68
|
+
unless parts.length < 4
|
69
|
+
next unless parts[0].eql?(category) || category.eql?("all")
|
70
|
+
architecture = parts[3].split(/-/)
|
71
|
+
data << {
|
72
|
+
:category => parts[0],
|
73
|
+
:suitename => parts[1],
|
74
|
+
:component => parts[2],
|
75
|
+
:architecture_dir => parts[3],
|
76
|
+
:architecture => architecture[1],
|
77
|
+
:fullpath => File.join(Cfg.rootdir, entry)
|
78
|
+
}
|
79
|
+
end
|
80
|
+
end
|
81
|
+
data
|
82
|
+
end
|
83
|
+
|
84
|
+
# Returns all directories without @rootdir
|
85
|
+
def self.all
|
86
|
+
components = Component.all
|
87
|
+
dirs = []
|
88
|
+
rootdir = Cfg.rootdir
|
89
|
+
components.each do |component|
|
90
|
+
architectures = Dir.glob(File.join(rootdir, component, "*"))
|
91
|
+
architectures.each do |architecture|
|
92
|
+
dirs.push architecture.gsub(/#{rootdir}\//, '') if File.directory? architecture
|
93
|
+
end
|
94
|
+
end
|
95
|
+
return dirs
|
96
|
+
end
|
97
|
+
|
98
|
+
# Gets all configured architectures
|
99
|
+
def self.allowed
|
100
|
+
Cfg.architectures.uniq
|
101
|
+
end
|
102
|
+
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
@@ -0,0 +1,335 @@
|
|
1
|
+
require 'repomate'
|
2
|
+
require 'date'
|
3
|
+
require 'time'
|
4
|
+
require 'colors'
|
5
|
+
|
6
|
+
# RepoMate module
|
7
|
+
module RepoMate
|
8
|
+
|
9
|
+
# Class containing the main logic
|
10
|
+
class Base
|
11
|
+
|
12
|
+
# Init
|
13
|
+
def initialize
|
14
|
+
FileUtils.mkdir_p(Cfg.rootdir)
|
15
|
+
|
16
|
+
@repository = Repository.new
|
17
|
+
@metafile = Metafile.new
|
18
|
+
@cpdbfile = File.join(Cfg.rootdir, "checkpoints.db")
|
19
|
+
@cpdb = Database.new(@cpdbfile)
|
20
|
+
|
21
|
+
unless Dir.exists?(Cfg.logdir)
|
22
|
+
puts
|
23
|
+
puts "\tPlease run \"repomate setup\" first!".hl(:red)
|
24
|
+
puts
|
25
|
+
end
|
26
|
+
|
27
|
+
create_checkpoints_table
|
28
|
+
|
29
|
+
end
|
30
|
+
|
31
|
+
# Add's a package to the staging area
|
32
|
+
def stage(workload)
|
33
|
+
workload.each do |entry|
|
34
|
+
@repository.create(entry[:suitename], entry[:component])
|
35
|
+
|
36
|
+
package = Package.new(entry[:package_fullname], entry[:suitename], entry[:component])
|
37
|
+
destination = Component.new(entry[:component], entry[:suitename], "stage")
|
38
|
+
|
39
|
+
FileUtils.copy(entry[:package_fullname], File.join(destination.directory, package.newbasename))
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
# Returns a list of staged packages for cli confirmation packed as array of hashes
|
44
|
+
def prepare_publish
|
45
|
+
workload = []
|
46
|
+
|
47
|
+
source_category = "stage"
|
48
|
+
destination_category = "pool"
|
49
|
+
|
50
|
+
Component.dataset(source_category).each do |entry|
|
51
|
+
source = Component.new(entry[:component], entry[:suitename], source_category)
|
52
|
+
source.files.each do |fullname|
|
53
|
+
package = Package.new(fullname, entry[:suitename], entry[:component])
|
54
|
+
destination = Architecture.new(package.architecture, entry[:component], entry[:suitename], destination_category)
|
55
|
+
|
56
|
+
workload << {
|
57
|
+
:source_fullname => fullname,
|
58
|
+
:destination_fullname => File.join(destination.directory, package.newbasename),
|
59
|
+
:component => entry[:component],
|
60
|
+
:suitename => entry[:suitename],
|
61
|
+
:architecture => package.architecture
|
62
|
+
}
|
63
|
+
end
|
64
|
+
end
|
65
|
+
workload
|
66
|
+
end
|
67
|
+
|
68
|
+
# Publish all staged packages. Packages will be moved from stage to pool and linked to dists
|
69
|
+
def publish(workload)
|
70
|
+
newworkload = []
|
71
|
+
workload.each do |entry|
|
72
|
+
destination = Architecture.new(entry[:architecture], entry[:component], entry[:suitename], "dists")
|
73
|
+
basename = File.basename(entry[:source_fullname])
|
74
|
+
|
75
|
+
@repository.create(entry[:suitename], entry[:component], entry[:architecture])
|
76
|
+
|
77
|
+
newworkload << {
|
78
|
+
:source_fullname => entry[:destination_fullname],
|
79
|
+
:destination_dir => destination.directory,
|
80
|
+
:component => entry[:component],
|
81
|
+
:suitename => entry[:suitename],
|
82
|
+
:architecture => entry[:architecture]
|
83
|
+
}
|
84
|
+
FileUtils.move(entry[:source_fullname], entry[:destination_fullname])
|
85
|
+
end
|
86
|
+
workload = newworkload
|
87
|
+
|
88
|
+
save_checkpoint
|
89
|
+
check_versions(workload)
|
90
|
+
end
|
91
|
+
|
92
|
+
# Does the link job after checking versions through dpkg
|
93
|
+
def check_versions(workload)
|
94
|
+
dpkg = Cfg.dpkg
|
95
|
+
|
96
|
+
raise "dpkg is not installed" unless File.exists?(dpkg)
|
97
|
+
|
98
|
+
link_workload = []
|
99
|
+
unlink_workload = []
|
100
|
+
|
101
|
+
workload.each do |entry|
|
102
|
+
source_package = Package.new(entry[:source_fullname], entry[:suitename], entry[:component])
|
103
|
+
destination_fullname = File.join(entry[:destination_dir], source_package.newbasename)
|
104
|
+
|
105
|
+
Dir.glob("#{entry[:destination_dir]}/#{source_package.name}*.deb") do |target_fullname|
|
106
|
+
target_package = Package.new(target_fullname, entry[:suitename], entry[:component] )
|
107
|
+
|
108
|
+
if system("#{dpkg} --compare-versions #{source_package.version} gt #{target_package.version}")
|
109
|
+
puts "Package: #{target_package.newbasename} will be replaced with #{source_package.newbasename}"
|
110
|
+
unlink_workload << {
|
111
|
+
:destination_fullname => target_fullname,
|
112
|
+
:newbasename => target_package.newbasename
|
113
|
+
}
|
114
|
+
elsif system("#{dpkg} --compare-versions #{source_package.version} eq #{target_package.version}")
|
115
|
+
puts "Package: #{source_package.newbasename} already exists with same version"
|
116
|
+
return
|
117
|
+
elsif system("#{dpkg} --compare-versions #{source_package.version} lt #{target_package.version}")
|
118
|
+
puts "Package: #{source_package.newbasename} already exists with higher version"
|
119
|
+
return
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
link_workload << {
|
124
|
+
:source_fullname => source_package.fullname,
|
125
|
+
:destination_fullname => destination_fullname,
|
126
|
+
:suitename => source_package.suitename,
|
127
|
+
:component => source_package.component,
|
128
|
+
:newbasename => source_package.newbasename
|
129
|
+
}
|
130
|
+
end
|
131
|
+
|
132
|
+
unlink(unlink_workload)
|
133
|
+
link(link_workload)
|
134
|
+
end
|
135
|
+
|
136
|
+
# links the workload
|
137
|
+
def link(workload)
|
138
|
+
action = false
|
139
|
+
|
140
|
+
workload.each do |entry|
|
141
|
+
@repository.create(entry[:suitename], entry[:component], entry[:architecture])
|
142
|
+
unless File.exists?(entry[:destination_fullname])
|
143
|
+
package = Package.new(entry[:source_fullname], entry[:suitename], entry[:component])
|
144
|
+
package.create_checksums
|
145
|
+
|
146
|
+
File.symlink(entry[:source_fullname], entry[:destination_fullname])
|
147
|
+
puts "Package: #{package.newbasename} linked to production => #{entry[:suitename]}/#{entry[:component]}"
|
148
|
+
action = true
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
if action
|
153
|
+
@metafile.create
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
# unlinks workload
|
158
|
+
def unlink(workload)
|
159
|
+
action = false
|
160
|
+
|
161
|
+
workload.each do |entry|
|
162
|
+
package = Package.new(entry[:destination_fullname], entry[:suitename], entry[:component])
|
163
|
+
package.delete_checksums
|
164
|
+
|
165
|
+
File.unlink(entry[:destination_fullname])
|
166
|
+
puts "Package: #{package.newbasename} unlinked"
|
167
|
+
action = true
|
168
|
+
end
|
169
|
+
|
170
|
+
if action
|
171
|
+
cleandirs
|
172
|
+
@metafile.create
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
# Create the checkpoint table
|
177
|
+
def create_checkpoints_table
|
178
|
+
sql = "create table if not exists checkpoints (
|
179
|
+
date varchar(25),
|
180
|
+
suitename varchar(10),
|
181
|
+
component varchar(10),
|
182
|
+
architecture varchar(10),
|
183
|
+
basename varchar(70)
|
184
|
+
)"
|
185
|
+
@cpdb.query(sql)
|
186
|
+
end
|
187
|
+
|
188
|
+
# Saves a checkpoint
|
189
|
+
def save_checkpoint
|
190
|
+
datetime = DateTime.now
|
191
|
+
source_category = "dists"
|
192
|
+
|
193
|
+
Architecture.dataset(source_category).each do |entry|
|
194
|
+
source = Architecture.new(entry[:architecture], entry[:component], entry[:suitename], source_category)
|
195
|
+
source.files.each do |fullname|
|
196
|
+
basename = File.basename(fullname)
|
197
|
+
@cpdb.query("insert into checkpoints values ( '#{datetime}', '#{entry[:suitename]}', '#{entry[:component]}', '#{entry[:architecture]}', '#{basename}' )")
|
198
|
+
end
|
199
|
+
end
|
200
|
+
|
201
|
+
puts "Checkpoint (#{datetime.strftime("%F %T")}) saved"
|
202
|
+
end
|
203
|
+
|
204
|
+
# Loads a checkpoint
|
205
|
+
def load_checkpoint(number)
|
206
|
+
list = get_checkpoints
|
207
|
+
link_workload = []
|
208
|
+
unlink_workload = []
|
209
|
+
source_category = "dists"
|
210
|
+
|
211
|
+
Architecture.dataset(source_category).each do |entry|
|
212
|
+
destination = Architecture.new(entry[:architecture], entry[:component], entry[:suitename], source_category)
|
213
|
+
destination.files.each do |fullname|
|
214
|
+
unlink_workload << {
|
215
|
+
:destination_fullname => fullname,
|
216
|
+
:component => entry[:component],
|
217
|
+
:suitename => entry[:suitename],
|
218
|
+
:architecture => entry[:architecture]
|
219
|
+
}
|
220
|
+
end
|
221
|
+
end
|
222
|
+
|
223
|
+
@cpdb.query("select date, suitename, component, architecture, basename from checkpoints").each do |row|
|
224
|
+
if row[0] == list[number]
|
225
|
+
suitename = row[1]
|
226
|
+
component = row[2]
|
227
|
+
architecture = row[3]
|
228
|
+
basename = row[4]
|
229
|
+
source = Architecture.new(architecture, component, suitename, "pool")
|
230
|
+
destination = Architecture.new(architecture, component, suitename, "dists")
|
231
|
+
|
232
|
+
link_workload << {
|
233
|
+
:source_fullname => File.join(source.directory, basename),
|
234
|
+
:destination_fullname => File.join(destination.directory, basename),
|
235
|
+
:component => component,
|
236
|
+
:suitename => suitename,
|
237
|
+
:architecture => architecture
|
238
|
+
}
|
239
|
+
end
|
240
|
+
end
|
241
|
+
|
242
|
+
unlink(unlink_workload)
|
243
|
+
link(link_workload)
|
244
|
+
end
|
245
|
+
|
246
|
+
# Returns a list of checkpoints for the cli
|
247
|
+
def get_checkpoints
|
248
|
+
order = 0
|
249
|
+
dates = []
|
250
|
+
list = {}
|
251
|
+
|
252
|
+
@cpdb.query("select date from checkpoints group by date order by date asc").each do |row|
|
253
|
+
dates << row.first
|
254
|
+
end
|
255
|
+
|
256
|
+
dates.each do |date|
|
257
|
+
order += 1
|
258
|
+
list[order] = date
|
259
|
+
end
|
260
|
+
|
261
|
+
list
|
262
|
+
end
|
263
|
+
|
264
|
+
# Returns a list of packages
|
265
|
+
def get_packagelist(category)
|
266
|
+
packages = []
|
267
|
+
if category.eql?("stage")
|
268
|
+
Component.dataset(category).each do |entry|
|
269
|
+
source = Component.new(entry[:component], entry[:suitename], category)
|
270
|
+
source.files.each do |fullname|
|
271
|
+
package = Package.new(fullname, entry[:suitename], entry[:component])
|
272
|
+
|
273
|
+
packages << {
|
274
|
+
:fullname => fullname,
|
275
|
+
:controlfile => package.controlfile,
|
276
|
+
:component => entry[:component],
|
277
|
+
:suitename => entry[:suitename]
|
278
|
+
}
|
279
|
+
end
|
280
|
+
end
|
281
|
+
else
|
282
|
+
Architecture.dataset(category).each do |entry|
|
283
|
+
source = Architecture.new(entry[:architecture], entry[:component], entry[:suitename], category)
|
284
|
+
source.files.each do |fullname|
|
285
|
+
package = Package.new(fullname, entry[:suitename], entry[:component])
|
286
|
+
|
287
|
+
packages << {
|
288
|
+
:fullname => fullname,
|
289
|
+
:controlfile => package.controlfile,
|
290
|
+
:component => entry[:component],
|
291
|
+
:suitename => entry[:suitename],
|
292
|
+
:architecture => entry[:architecture]
|
293
|
+
}
|
294
|
+
end
|
295
|
+
end
|
296
|
+
end
|
297
|
+
packages
|
298
|
+
end
|
299
|
+
|
300
|
+
# cleans up unused directories
|
301
|
+
def cleandirs
|
302
|
+
action = false
|
303
|
+
|
304
|
+
@repository.categories.each do |category|
|
305
|
+
next if category.eql?("stage")
|
306
|
+
Architecture.dataset(category).each do |entry|
|
307
|
+
directory = Architecture.new(entry[:architecture], entry[:component], entry[:suitename], category)
|
308
|
+
if directory.is_unused?(entry[:fullpath])
|
309
|
+
action = true
|
310
|
+
directory.destroy
|
311
|
+
end
|
312
|
+
end
|
313
|
+
Component.dataset(category).each do |entry|
|
314
|
+
directory = Component.new(entry[:component], entry[:suitename], category)
|
315
|
+
if directory.is_unused?(entry[:fullpath])
|
316
|
+
action = true
|
317
|
+
directory.destroy
|
318
|
+
end
|
319
|
+
end
|
320
|
+
Suite.dataset(category).each do |entry|
|
321
|
+
directory = Suite.new(entry[:suitename], category)
|
322
|
+
if directory.is_unused?(entry[:fullpath])
|
323
|
+
action = true
|
324
|
+
directory.destroy
|
325
|
+
end
|
326
|
+
end
|
327
|
+
end
|
328
|
+
if action
|
329
|
+
puts "Cleaning structure"
|
330
|
+
@metafile.create
|
331
|
+
end
|
332
|
+
end
|
333
|
+
end
|
334
|
+
end
|
335
|
+
|
@@ -0,0 +1,60 @@
|
|
1
|
+
# RepoMate module
|
2
|
+
module RepoMate
|
3
|
+
|
4
|
+
# Class for the category layer of the directory structure
|
5
|
+
class Category
|
6
|
+
|
7
|
+
# Init
|
8
|
+
def initialize(category)
|
9
|
+
@category = category
|
10
|
+
end
|
11
|
+
|
12
|
+
# Returns the name of the category (eg. pool, dists)
|
13
|
+
def name
|
14
|
+
@category
|
15
|
+
end
|
16
|
+
|
17
|
+
# Returns the full path of the categories directory
|
18
|
+
def directory
|
19
|
+
File.join(Cfg.rootdir, @category)
|
20
|
+
end
|
21
|
+
|
22
|
+
# Checks if the category directory exists
|
23
|
+
def exist?
|
24
|
+
Dir.exist?(directory)
|
25
|
+
end
|
26
|
+
|
27
|
+
# Creates the directory strcuture of the category
|
28
|
+
def create
|
29
|
+
FileUtils.mkdir_p(directory) unless exist?
|
30
|
+
end
|
31
|
+
|
32
|
+
# Deletes a categories directory
|
33
|
+
def destroy
|
34
|
+
FileUtils.rm_r(directory) if exist?
|
35
|
+
end
|
36
|
+
|
37
|
+
# Returns a dataset including the name of the category and the fullpath
|
38
|
+
def self.dataset(category=nil)
|
39
|
+
data = []
|
40
|
+
self.all.each do |entry|
|
41
|
+
unless entry.nil?
|
42
|
+
next unless entry.eql?(category) || category.eql?("all")
|
43
|
+
data << {
|
44
|
+
:category => entry,
|
45
|
+
:fullpath => File.join(Cfg.rootdir, entry)
|
46
|
+
}
|
47
|
+
end
|
48
|
+
end
|
49
|
+
data
|
50
|
+
end
|
51
|
+
|
52
|
+
# Returns all directories
|
53
|
+
def self.all
|
54
|
+
dirs = Dir.glob(File.join(Cfg.rootdir, "*"))
|
55
|
+
dirs.map{ |dir| File.basename(dir) unless dirs.include?(File.basename(dir)) }
|
56
|
+
end
|
57
|
+
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
data/lib/repomate/cli.rb
ADDED
@@ -0,0 +1,123 @@
|
|
1
|
+
require 'date'
|
2
|
+
require 'time'
|
3
|
+
|
4
|
+
# RepoMate module
|
5
|
+
module RepoMate
|
6
|
+
|
7
|
+
# Class for the commandline interface
|
8
|
+
class Cli
|
9
|
+
|
10
|
+
# Init
|
11
|
+
def initialize
|
12
|
+
@repomate = Base.new
|
13
|
+
@repository = Repository.new
|
14
|
+
end
|
15
|
+
|
16
|
+
# Sets up the base directory structure by calling the repository class
|
17
|
+
def setup(options)
|
18
|
+
if options.suitename?
|
19
|
+
@repository.create(options[:suitename], options[:component], options[:architecture])
|
20
|
+
else
|
21
|
+
STDERR.puts "Specify a suitename with [-s|--suitname]"
|
22
|
+
exit 1
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
# Adds a given package to the staging area by calling the base class
|
27
|
+
def stage(options, filename)
|
28
|
+
if options.suitename?
|
29
|
+
workload = []
|
30
|
+
workload << {:package_fullname => filename, :suitename => options[:suitename], :component => options[:component]}
|
31
|
+
|
32
|
+
puts "Package: #{filename} moving to stage => #{options[:suitename]}/#{options[:component]}"
|
33
|
+
|
34
|
+
@repomate.stage(workload)
|
35
|
+
else
|
36
|
+
STDERR.puts "Specify a suitename with [-s|--suitname]"
|
37
|
+
exit 1
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
# Get's all packages from the staging area. Packages need to be confirmed here.
|
42
|
+
def publish(options)
|
43
|
+
@repomate.prepare_publish.each do |entry|
|
44
|
+
workload = []
|
45
|
+
basename = File.basename(entry[:source_fullname])
|
46
|
+
suitename = entry[:suitename]
|
47
|
+
component = entry[:component]
|
48
|
+
|
49
|
+
unless options.force?
|
50
|
+
printf "\n%s", "Link #{basename} to production => #{suitename}/#{component}? [y|yes|n|no]: "
|
51
|
+
input = STDIN.gets
|
52
|
+
end
|
53
|
+
|
54
|
+
if options.force? || input =~ /(y|yes)/
|
55
|
+
workload << {
|
56
|
+
:source_fullname => entry[:source_fullname],
|
57
|
+
:destination_fullname => entry[:destination_fullname],
|
58
|
+
:component => entry[:component],
|
59
|
+
:suitename => entry[:suitename],
|
60
|
+
:architecture => entry[:architecture]
|
61
|
+
}
|
62
|
+
end
|
63
|
+
@repomate.publish(workload) unless workload.empty?
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
# Save a checkpoint
|
68
|
+
def save_checkpoint
|
69
|
+
# Add verification and some output here
|
70
|
+
@repomate.save_checkpoint
|
71
|
+
end
|
72
|
+
|
73
|
+
# List all packages, see cli output
|
74
|
+
def list_packagelist(options)
|
75
|
+
if options.repodir?
|
76
|
+
architecture = "unknown"
|
77
|
+
|
78
|
+
packages = @repomate.get_packagelist(options[:repodir])
|
79
|
+
packages.each do |package|
|
80
|
+
architecture = package[:architecture] if package[:architecture]
|
81
|
+
printf "%-50s%-20s%s\n", package[:controlfile]['Package'], package[:controlfile]['Version'], "#{package[:suitename]}/#{package[:component]}/#{architecture}"
|
82
|
+
end
|
83
|
+
else
|
84
|
+
STDERR.puts "Specify a category with [-r|--repodir]"
|
85
|
+
exit 1
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
# Choose a checkpoint to restore.
|
90
|
+
def choose_checkpoint
|
91
|
+
list = @repomate.get_checkpoints
|
92
|
+
|
93
|
+
if list.empty?
|
94
|
+
STDERR.puts "We can't restore because we don't have checkpoints"
|
95
|
+
exit 1
|
96
|
+
end
|
97
|
+
|
98
|
+
puts "\n*** Restore production links to a date below. ***
|
99
|
+
Remember: If you need to restore, the last entry might be the one you want!
|
100
|
+
Everything between the last two \"publish (-P) commands\" will be lost if you proceed!\n\n"
|
101
|
+
|
102
|
+
list.each do |num, date|
|
103
|
+
datetime = DateTime.parse(date)
|
104
|
+
puts "#{num}) #{datetime.strftime("%F %T")}"
|
105
|
+
end
|
106
|
+
|
107
|
+
printf "\n%s", "Enter number or [q|quit] to abord: "
|
108
|
+
input = STDIN.gets
|
109
|
+
number = input.to_i
|
110
|
+
|
111
|
+
if input =~ /(q|quit)/
|
112
|
+
puts "Aborting..."
|
113
|
+
exit 0
|
114
|
+
elsif list[number].nil?
|
115
|
+
STDERR.puts "Invalid number"
|
116
|
+
exit 1
|
117
|
+
else
|
118
|
+
@repomate.load_checkpoint(number)
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|