workEnv 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,1004 @@
1
+ # -*- coding: utf-8 -*-
2
+ require 'yaml'
3
+ require 'pathname'
4
+ require 'thread'
5
+ require 'base64'
6
+ # User overridable defines
7
+
8
+
9
+ module WorkEnvs
10
+ # User login
11
+ USERNAME=`whoami`.chomp()
12
+
13
+ # Defautl directory to put envs to
14
+ WORK_ENVS=(ENV["WORK_ENVS"] != nil && ENV["WORK_ENVS"] != "") ?
15
+ ENV["WORK_ENVS"] : "/work1/#{USERNAME}/work-envs"
16
+
17
+ # Name of environment config file (YAML serialized) within the environment directory
18
+ ENV_CONF=".env_config"
19
+
20
+ # Name of the script to switch into an end
21
+ ENV_SWITCH=".switch_env"
22
+
23
+ # Default expiration date for licenses
24
+ DEFAULT_EXPIRATION_DATE="12/31/2025"
25
+
26
+ # Version of the #Core and #WorkEnv object used for migration
27
+ WORK_ENV_VERSION = 10
28
+
29
+ # Core Env class
30
+ # This is not actually an env but all the methods Env needs to inherit through the WorkEnv class
31
+ # that provide global services accross the env graph
32
+ class Core
33
+ #
34
+ # WEIRD Methods. Object methods used to provide easy access to class methods all the way down
35
+ #
36
+
37
+ # Calls getVersion for all the nodes in the env graph
38
+ #
39
+ # Returns a hash containing each env version
40
+ def getVersion(canFail = false)
41
+ versions = {}
42
+ path = self.genPath()
43
+ WorkEnvs::familyTreeApply(self) {|x|
44
+ envType = WorkEnvs::getEnvType(x)
45
+ begin
46
+ if !@deps_infos.exists?(envType) then
47
+ next
48
+ end
49
+ versions.merge!(x.getVersion(path, canFail)){|key, v1, v2|
50
+ raise("Incoherency for version of #{key}. Have both #{v1} and #{v2}")
51
+ }
52
+ rescue => e
53
+ versions[envType] = :unitialized
54
+ raise e if @setup_options[WENV_OPTS_PARTIAL] != "true"
55
+ end
56
+ }
57
+ return versions
58
+ end
59
+
60
+ # Calls checkEnv for all the nodes in the env graph that are installed.
61
+ #
62
+ # The goal is to make sure the checkouted files use the expected version
63
+ #
64
+ # Returns if environment valid (if dev or Partial, always valid).
65
+ # Raises an exception if it is not
66
+ def checkEnv(versions = nil)
67
+ #Disable for the moment as this never reported any issues
68
+ return true
69
+
70
+ # return if isDev?()
71
+ # path = self.genPath()
72
+ # WorkEnvs::familyTreeApply(self) {|x|
73
+ # next if versions[WorkEnvs::getEnvType(x)] == nil
74
+ # begin
75
+ # x.checkEnv(self, path,versions)
76
+ # rescue => e
77
+ # raise e if @setup_options[WENV_OPTS_PARTIAL] != "true"
78
+ # end
79
+ # }
80
+ end
81
+
82
+
83
+ #Check that all packages in the DB with this SHA1 are known by workEnvs
84
+ def checkPackages(downloader, revision, objClass)
85
+ allPackages = WorkEnvs::listAllPackagesWithSiblings(objClass)
86
+ availPackages = downloader.queryMatchingSHA1(revision)
87
+ unknownPackages = availPackages - allPackages
88
+ if unknownPackages.length != 0 then
89
+ STDERR.puts "ERROR: #{objClass} -> Found packages registered with SHA1 #{revision} in the DB but not in WorkEnvs."+
90
+ " Try to update WorkEnvs by running:\n#{WORK_ENV_SCRIPTS_DIR}/wenv --update\n"+
91
+ "If the problem subsists, someone added a new package in the DB but did not register it in the WorkEnvs\n"+
92
+ "Find him and slap him repeatedly with a trout until he fixes it....\n"+
93
+ "Unknown packages are:"
94
+ unknownPackages.each(){|p|
95
+ package = downloader.queryPackage(p, revision, true)
96
+ STDERR.puts"\t#{p} => #{package.pack}"
97
+ }
98
+ exit(1) if ENV["WENV_IGNORE_ERRORS"] == nil
99
+ end
100
+ end
101
+
102
+ # Analyze the packages from a sub env and look for dependy to other sub envs
103
+ #
104
+ # - classDeps is a list of the sub env class depency description
105
+ # - familyTree is a list of all sub env class that are parents to self
106
+ # - packages is a list of all the available packages provided by this sub env class
107
+ # for this version
108
+ # - info is the Depencies object used during dependcy computation
109
+ #
110
+ # Used only by genPath
111
+ #
112
+ # Throws EmptyQueryException if a dependency to a parent sub class could not be fulfilled
113
+ # or mismatch and existing one.
114
+ def lookupDependencies(downloader, classDeps, familyTree, packages, infos)
115
+ packages.each(){|pack|
116
+ # Match all packages against regexps
117
+ dependencies = nil
118
+ classDeps.each(){|regexp, deps|
119
+ if pack.to_s =~ regexp then
120
+ #Generate dependencies
121
+ dependencies = downloader.listDependencies(pack) if dependencies == nil
122
+ deps.each(){|depTypes, depList|
123
+ depTypes = [ depTypes ] if ! depTypes.kind_of? Array
124
+ depClasses = depTypes.map(){|depType| WorkEnvs::symbolToClass(depType)}
125
+ # There may only be one element not as an array so self wrap it
126
+ if(depList.kind_of? String) then
127
+ depList = [ depList ]
128
+ end
129
+ depList.each(){|dep_name|
130
+ dep_ver = downloader.extractDependency(dependencies, dep_name)
131
+ next if dep_ver == nil
132
+ success = false
133
+ depClasses.each(){|depClass|
134
+ if familyTree.index(depClass) == nil then
135
+ STDERR.puts "DEV WARNING: Found requested dependency from #{pack} to"+
136
+ " #{dep_name} (#{depClass}) (#{dep_ver}) " +
137
+ "but environment #{depClass} is not a valid ancestor"
138
+ end
139
+ begin
140
+ infos.push_version(depClass, dep_ver)
141
+ success = true
142
+ rescue EmptyQueryException
143
+ end
144
+ }
145
+ if success == false then
146
+ STDERR.puts "WARNING: Package #{pack} has dependencies to any of those "+
147
+ "env (#{depClasses}), but no matching packages were found"
148
+ end
149
+ }
150
+ }
151
+ end
152
+ }
153
+ }
154
+ end
155
+ private :lookupDependencies
156
+
157
+ # Generate a list of all the packages (including temporary) that should
158
+ # be downloaded and extracted in the env.
159
+ #
160
+ # This looks at the dependencies both in the options (--sha1, --version, --rev-files, etc)
161
+ # and the dependencies from the packages while adding them to the list.
162
+ #
163
+ # It will iterate on all the env dependency tree (except if opts[:no_deps] is true)
164
+ # to generate a complete list.
165
+ #
166
+ # It returns two arrays:
167
+ # - an array of Package object containg all the permanent packages
168
+ # - an array of temporary Package object that might be needed furing the post_setup phase
169
+ def genPackages(downloader, opts)
170
+ packages = []
171
+ temp_packages = []
172
+ infos = opts[:infos]
173
+
174
+ settings = WorkEnvs::settings()
175
+
176
+ #In case of RPC chaining, check the previous one to make sure we won't loop
177
+ prev_rpc_host=""
178
+ begin
179
+ prev_rpc_host = opts[:settings][:control][:rSettings][:settings][:db][:rpc_host]
180
+ rescue => e
181
+ # Ignore errors
182
+ end
183
+ # Try to do it remotely but do not recurse infinitely
184
+ if settings[:db][:rpc_host] != nil && prev_rpc_host != settings[:db][:rpc_host] then
185
+ msgs = nil
186
+ host = settings[:db][:rpc_host]
187
+ begin
188
+ rOpts={}
189
+ rOpts[:settings] = WorkEnvs::settings()
190
+ rOpts[:opts] = opts
191
+
192
+ # Force type when used with wenv update
193
+ # Wenv query has already set this but it's
194
+ # the same value as @type
195
+ rOpts[:opts][:type] = @type
196
+ objs, msgs = WorkEnvs::remoteRun(host, "query",
197
+ ' -R "' + WorkEnvs::serialize(rOpts) + '"')
198
+ hash = objs[0]
199
+ packages = hash[:packages]
200
+ temp_packages = hash[:temp_packages]
201
+ puts "INFO: Computed dependencies remotely on #{host}"
202
+ puts msgs
203
+ return packages, temp_packages
204
+ rescue => e
205
+ #Fail, fall back to original mode
206
+ puts "INFO: Fail to resolve depencies remotely. Running locally (#{e.to_s})"
207
+ end
208
+ end
209
+
210
+ familyTree = WorkEnvs::familyTreeList(self)
211
+
212
+ packageRef={}
213
+
214
+ WorkEnvs::familyTreeApply(self, false){|x|
215
+ envType = WorkEnvs::getEnvType(x)
216
+ packName = WorkEnvs::getMainPackage(x)
217
+
218
+ # We hit rock bottom or empty env
219
+ next if packName == ""
220
+
221
+ # If we allow partial env and have no deps on this package, just skip it
222
+ next if infos.partialEnv == true && !infos.exists?(envType)
223
+ # Find the revision from the dep
224
+ if infos.exists?(envType) then
225
+ revision = infos.get_sha1(envType)
226
+ if revision !~ /^[a-f0-9]{40}$/ then
227
+ raise("Invalid #{WorkEnvs::getMainPackage(x)} version '#{infos.get(envType)}'")
228
+ end
229
+ else
230
+ if opts[:shutUp] != true && !x.isDev?() && opts[:no_deps] != true then
231
+ puts "WARNING: Found no dependency to #{envType.to_s}. Skipping..."
232
+ end
233
+ next
234
+ end
235
+
236
+ classPackages = WorkEnvs::getPackages(x)
237
+ classDeps = WorkEnvs::getDependencies(x)
238
+ classDeps = {} if opts[:no_deps] == true
239
+
240
+ # First let us check that we know of every package registered with this SHA1 in the db
241
+ # This is just for sanity
242
+ checkPackages(downloader, revision, x)
243
+
244
+ WorkEnvs::dputs("Looking at #{envType} packages:")
245
+ isExternal = (opts[:envOpts] != nil && opts[:envOpts][WENV_OPTS_EXTERNAL].to_s == "true") ? true : false
246
+ # Get required external (and internal if enabled) list of packages
247
+ required = classPackages[:external][:required] + (isExternal ? [] : classPackages[:internal][:required])
248
+ # Get extra external (and internal if enabled) list of packages
249
+ # Extra means it's OK if not available
250
+ extras = classPackages[:external][:extras] + (isExternal ? [] : classPackages[:internal][:extras])
251
+
252
+ # Generate the list of truly available package and their full package name
253
+ # for both required and extras, and both permanent and temporary
254
+ local_packages = downloader.getPackagesName(required, extras, revision)
255
+ local_temp_packages = downloader.getPackagesName(classPackages[:temporary][:required],
256
+ classPackages[:temporary][:extras], revision)
257
+
258
+ all_packages = local_packages + local_temp_packages
259
+ if isExternal
260
+ # Still check the internal packages for dependencies
261
+ all_packages += downloader.getPackagesName(classPackages[:internal][:required],
262
+ classPackages[:internal][:extras], revision)
263
+ end
264
+
265
+ if opts[:skipDeps] != true then
266
+ # Check in the package list we generated if there are RPM/Deb
267
+ # dependencies that matches env Class description.
268
+ # This is how dependencies are propagated when working only with package
269
+ # and without any rev_file/sub-sha1 options
270
+ lookupDependencies(downloader, classDeps, familyTree, all_packages, infos)
271
+ end
272
+
273
+
274
+ # Sanity checks to make sure there are never two sub envs that pull the same package
275
+ # in two different version
276
+ #
277
+ # packageRef basically store the full name of the packahe
278
+ # If another envs pull sthe exact same one it's OK
279
+ # If it pull the same packahe name but with a different full name (ie different version or
280
+ # due to a weird rebuild after tag changes), let it know and crash
281
+ (local_packages + local_temp_packages).each(){|pack|
282
+ if packageRef[pack.name] == nil then
283
+ packageRef[pack.name] = { :pack => pack.pack, :env => x }
284
+ next
285
+ end
286
+ extname = File.extname(pack.pack)
287
+ next if extname != ".deb" && extname != ".rpm"
288
+ if packageRef[pack.name][:pack] != pack.pack then
289
+ raise("Env pulls both #{packageRef[pack.name][:pack]} (#{packageRef[pack.name][:env]}) and "+
290
+ "#{pack.pack} (#{x}) which are not compatible")
291
+ end
292
+ }
293
+ packages += local_packages
294
+ temp_packages += local_temp_packages
295
+
296
+ }
297
+ packages.uniq!
298
+ temp_packages.uniq!
299
+
300
+ checkGitDeps(downloader, infos) if opts[:checkGitDependencies]
301
+
302
+ return packages, temp_packages
303
+ end
304
+
305
+ # Checks that dependencies extracted from the packages math the Git dependencies.
306
+ def checkGitDeps(downloader, infos)
307
+ git_repo = WorkEnvs::settings[:db][:git_repo]
308
+ WorkEnvs::familyTreeApply(self){|x|
309
+ envClass = x
310
+ envType = WorkEnvs::getEnvType(x)
311
+ version = infos.get_version(envType)
312
+ next if version == nil
313
+
314
+ gitRepo = WorkEnvs::getGitRepo(x).to_s()
315
+ next if gitRepo == ""
316
+
317
+ parentList = WorkEnvs::familyTreeListClass(x)
318
+
319
+ dInfos = downloader.queryDepInfosFromVersion(x, version, true)
320
+ sha1 = dInfos[:sha1]
321
+
322
+ remoteRepo = gitRepo.gsub(/^.*:/, '')
323
+ revFiles = runCmd("ssh #{git_repo} ls-tree #{remoteRepo} #{sha1} -- valid/hudson/rev_files/", !VERBOSE)
324
+ revFiles.split("\n").each(){|revFileLine|
325
+ cols = revFileLine.split(" ")
326
+ revFile = File.basename(cols[3])
327
+ revClasses = WorkEnvs::revFileToClasses(revFile)
328
+ next if revClasses == nil
329
+
330
+ revClasses.each(){|revClass|
331
+ revType = WorkEnvs::getEnvType(revClass)
332
+ # Continue if this is not a parent env...
333
+ next if parentList.index(revClass) == nil
334
+
335
+ puts "Checking #{envType} => #{revType}"
336
+ objSha1 = cols[2]
337
+ revSha1 = runCmd("ssh #{git_repo} cat-file #{remoteRepo} -p #{objSha1}", !VERBOSE)
338
+
339
+ revPack = downloader.queryPackageVersion(WorkEnvs::getMainPackage(revClass), revSha1, false)
340
+ expected = infos.get_version(revType)
341
+
342
+ #Skip if everything is OK
343
+ next if expected == revPack && expected.to_s != ""
344
+
345
+ expected = "<N/A>" if expected.to_s == ""
346
+ revPack = "<???>" if revPack.to_s == ""
347
+ puts "WARNING: On #{envType} => #{revType}\n" +
348
+ "\tRPM requires: #{expected} / Rev files requires: #{revPack} (SHA1 = #{revSha1})"
349
+ }
350
+ }
351
+ }
352
+ end
353
+
354
+ # Generate the env specific part of the switch env script
355
+ #
356
+ # Iterates on all the env dependency tree and call the switchEnv method of each class.
357
+ #
358
+ # Note that switchEnv is not called for classes that have not been updated
359
+ # (no dependency to them even if in the dependency tree)
360
+ #
361
+ # Returns an array of commands to be written in the switchEnv script
362
+ def genSwitchEnv()
363
+ path = self.genPath()
364
+ commands=[]
365
+ WorkEnvs::familyTreeApply(self) {|x|
366
+ next if (@deps_infos == nil ||
367
+ !@deps_infos.exists?(WorkEnvs::getEnvType(x))) && !x.isDev?() && !self.isDev?()
368
+ begin
369
+ ret = x.switchEnv(self, "${WORK_ENV_PATH}")
370
+ commands = [ "##{x.to_s}" ] + ret + [ "" ] + commands
371
+ rescue => e
372
+ raise e if @setup_options[WENV_OPTS_PARTIAL] != "true"
373
+ end
374
+ }
375
+ return commands
376
+ end
377
+
378
+ # Default footer commands to output in the switch env script.
379
+ #
380
+ # - Autoload workEnv bash completion
381
+ # - Load global workEnv bashrc if it exists
382
+ # - Load env specific bashrc if it exists
383
+ # - Check Env coherency
384
+ def genEnvFooter()
385
+ array = []
386
+ array << "#Env Footer"
387
+ array << "[ -f ${WORK_ENV_SCRIPTS_DIR}/workrc-completion.sh ] && source ${WORK_ENV_SCRIPTS_DIR}/workrc-completion.sh"
388
+ array << "[ -f #{WORK_ENV_GLOBAL_DIR}/bashrcs/.bashrc ] && source #{WORK_ENV_GLOBAL_DIR}/bashrcs/.bashrc"
389
+ array << "[ -f #{WORK_ENV_GLOBAL_DIR}/bashrcs/${WORK_ENV_CURRENT} ] && source #{WORK_ENV_GLOBAL_DIR}/bashrcs/${WORK_ENV_CURRENT}"
390
+ array << "if [ -f $WORK_ENV_SCRIPTS_DIR/checkEnv ]; then ruby $WORK_ENV_SCRIPTS_DIR/checkEnv || exit 1; fi"
391
+ return array
392
+ end
393
+
394
+ # Top level function to generate a switchEnv script.
395
+ #
396
+ # Call #genSwitchEnv and #genEnvFooter then dumps the strings to the appropriate file
397
+ def genSwitchEnvScript()
398
+ cmds = genSwitchEnv() +
399
+ genEnvFooter()
400
+ output = File.open(self.genPath() + "/" + ENV_SWITCH, "w")
401
+ cmds.each(){|x|
402
+ output.puts x
403
+ }
404
+ output.close()
405
+ end
406
+
407
+ # Call the pre_setup method for each envClass
408
+ #
409
+ # Iterates on all the env dependency tree and calls the pre_setup method.
410
+ # Method is not called for dev env classes nor for uninitialized env classes
411
+ def pre_setup(opts, packages, temp_packages)
412
+ #Pre-setup in reverse order so we start from the trunk and install inherited env first
413
+ WorkEnvs::familyTreeApply(self, true){|x|
414
+ next if !@deps_infos.exists?(WorkEnvs::getEnvType(x)) && !x.isDev?()
415
+ next if x.singleton_methods().index(:pre_setup) == nil
416
+ x.pre_setup(opts, packages, temp_packages)
417
+ }
418
+ end
419
+
420
+ # Call the post_setup method for each envClass
421
+ #
422
+ # Iterates on all the env dependency tree and calls the post_setup method.
423
+ # Method is not called for dev env classes nor for uninitialized env classes
424
+ def post_setup(opts = {})
425
+ #Post setup in reverse order so we start from the trunk and install inherited env first
426
+ WorkEnvs::familyTreeApply(self, true){|x|
427
+ next if !@deps_infos.exists?(WorkEnvs::getEnvType(x)) && !x.isDev?()
428
+ next if x.singleton_methods().index(:post_setup) == nil
429
+ x.post_setup(opts)
430
+ }
431
+ genSwitchEnvScript()
432
+ end
433
+
434
+ # Call the pre_install method for each envClass
435
+ #
436
+ # Iterates on all the env dependency tree and calls the pre_install method.
437
+ # Method is not called for dev env classes nor for uninitialized env classes
438
+ def pre_install(opts, packages, temp_packages)
439
+ #Pre Install in reverse order so we start from the trunk and install inherited env first
440
+ WorkEnvs::familyTreeApply(self, true){|x|
441
+ next if !@deps_infos.exists?(WorkEnvs::getEnvType(x)) && !x.isDev?()
442
+ next if x.singleton_methods().index(:pre_install) == nil
443
+ x.pre_install(opts, packages, temp_packages)
444
+ }
445
+ end
446
+
447
+ # Call the post_install method for each envClass
448
+ #
449
+ # Iterates on all the env dependency tree and calls the post_install method.
450
+ # Method is not called for dev env classes nor for uninitialized env classes
451
+ def post_install(opts, packages, temp_packages)
452
+ #Post Install in reverse order so we start from the trunk and install inherited env first
453
+ WorkEnvs::familyTreeApply(self, true){|x|
454
+ next if !@deps_infos.exists?(WorkEnvs::getEnvType(x)) && !x.isDev?()
455
+ next if x.singleton_methods().index(:post_install) == nil
456
+ x.post_install(opts, packages, temp_packages)
457
+ }
458
+ end
459
+
460
+ #
461
+ # COMMON Methods. Same for all types of env.
462
+ #
463
+
464
+ # Return a if env has a dev type
465
+ #
466
+ # Dev env do not neet to be initialized to be switched to
467
+ def isDev?()
468
+ return self.class.isDev?()
469
+ end
470
+
471
+ # Rename an env
472
+ #
473
+ # Move the env directory and update the object name proprery
474
+ def rename(name)
475
+ raise("Cannot rename an environment with the same name...") if name == WorkEnvs::labelNameToStr(@label, @name)
476
+ raise("An environment named '#{name}' already exists") if WorkEnvs::existsEnv?(name)
477
+ label, name = WorkEnvs::strToLabelName(name)
478
+ runCmd("mv #{genPath()} "+
479
+ " #{WorkEnvs::getDirPathFromLabel(label)}/#{name}")
480
+ @name = name
481
+ @label = label
482
+ self.dump()
483
+ end
484
+
485
+ # Returns if the env can be switched to (env is initialized)
486
+ def isSwitchable?()
487
+ return true if @deps_infos != nil && @deps_infos.partialEnv == true
488
+
489
+ @versions.each() {|name, version|
490
+ if version == :uninitialized then
491
+ return false
492
+ end
493
+ }
494
+ return true
495
+ end
496
+
497
+ # Check that at least one version/SHA1 was required in the options
498
+ def check_version(opts = {})
499
+ downloader = get_downloader()
500
+ project = WorkEnvs::getMainPackage(self)
501
+ if opts[:version] != nil || opts[:sha1] != nil then
502
+ return downloader, opts[:infos].get_sha1(name)
503
+ elsif opts[:infos] != nil && !opts[:infos].empty?
504
+ return downloader, nil
505
+ else
506
+ raise("Neither version or SHA1 provided")
507
+ end
508
+ end
509
+
510
+ # Return a string with the patch to the environment directory
511
+ def genPath()
512
+ path = WorkEnvs::getDirPathFromLabel(@label) + "/" + @name
513
+ end
514
+
515
+ # Backup the environment config in its workspace
516
+ #
517
+ # This serializes the env as a YAML object into the environment directory
518
+ def dump()
519
+
520
+ raise("No path for environment") if @name.to_s() == ""
521
+
522
+
523
+ desc = File.open(genPath() + "/" + ENV_CONF, "w+")
524
+
525
+ label = @label
526
+ @label = nil
527
+ desc.puts self.to_yaml()
528
+ desc.close()
529
+ @label = label
530
+ end
531
+
532
+ # Returns a string that describe the env
533
+ #
534
+ # - lType == :name
535
+ # - name
536
+ # - lType == :short
537
+ # - name
538
+ # - type
539
+ # - machine
540
+ # - expiration
541
+ # - lType == :long
542
+ # - name
543
+ # - type
544
+ # - machine
545
+ # - expiration
546
+ # - versions info
547
+ # - setup options
548
+ def to_s(lType = :long)
549
+ lType = :long if lType == nil
550
+ case lType
551
+ when :short, :long
552
+ # Convert an environment to string so they can be printed
553
+ maxLen = WorkEnvs::getEnvNames().inject(0){|x, y| x > y.length ? x : y.length}
554
+ _versions = @versions
555
+ prefix=""
556
+ type = @type.to_s
557
+ name = WorkEnvs::labelNameToStr(@label, @name)
558
+ str = "* " +name.ljust(maxLen) + type.center(20) + @machine.to_s.center(15) + @expiration.to_s.center(15)
559
+
560
+ sub_shift = "".ljust(maxLen+20+15+15+5)
561
+
562
+ if lType == :long then
563
+ str+= "\t{"
564
+ _versions.each(){|key, val|
565
+ str += prefix +" :" + key.to_s + " => " + val.to_s + ""
566
+ prefix = "\n" + sub_shift + "\t "
567
+ }
568
+ str += " }"
569
+ if @setup_options != nil && @setup_options.empty? == false then
570
+ str+= "\n" + sub_shift + "\t{ "
571
+ comma = ""
572
+ @setup_options.each(){|name, val|
573
+ str += comma + name + "=" + val
574
+ comma = ", "
575
+ }
576
+ str += " }"
577
+ end
578
+ end
579
+ return str
580
+ when :name
581
+ str = WorkEnvs::labelNameToStr(@label, @name)
582
+ return str
583
+ else
584
+ raise("Unknown listing type #{lType}")
585
+ end
586
+ end
587
+
588
+ # List the latest top packages from the env type
589
+ #
590
+ # Query the latest limit packages defining this env (from the branch branch if non nil)
591
+ #
592
+ # Returns an array of EnvPackage
593
+ def getPackages(downloader, branch=nil, limit=100000)
594
+ branch='%' if branch.nil? || branch.length == 0
595
+
596
+ limit = 100000 if limit == 0
597
+ packages = downloader.queryPackages(WorkEnvs::getMainPackage(self), branch,
598
+ limit, WorkEnvs::getBlackList(self))
599
+ return packages
600
+ end
601
+
602
+ # Call getPackages and display the results on STDOUT
603
+ def listPackages(downloader, quantity, branch=nil)
604
+ puts "Getting package list..."
605
+ packages = getPackages(downloader, branch, quantity)
606
+ (packages.length - 1).downto(0){|x|
607
+ elnt = packages[x]
608
+ puts elnt
609
+ }
610
+ end
611
+
612
+ # Create a PackageDownloader with settings matching this env
613
+ def get_downloader(opts={})
614
+ machine = @machine
615
+ tables = @db_tables
616
+ begin
617
+ if opts[:settings][:arch][:machine] != nil then
618
+ arch = WorkEnvs::getArch(opts[:settings][:arch][:machine])
619
+
620
+ puts "INFO: Overriding environment arch with '#{arch[:label]}'" if machine != arch[:label]
621
+ machine = arch[:label]
622
+ end
623
+ rescue
624
+ # Probably no settings available
625
+ end
626
+ begin
627
+ if opts[:settings][:db][:package_default_table] != nil then
628
+ new_table = DBInterface::toTable(opts[:settings][:db][:package_default_table])
629
+
630
+ puts "INFO: Overriding environment table with '#{new_table}'" if tables != new_table
631
+ tables = new_table
632
+ end
633
+ rescue
634
+ # Probably no settings available
635
+ end
636
+ return WorkEnvs::PackageDownloader.new(machine, tables, !VERBOSE)
637
+ end
638
+
639
+ # Wrapper around genPackages
640
+ #
641
+ # Basically calls genPackages with a few extra checks.
642
+ #
643
+ # This is mostly use for get_dependencies or by queryEnv to generate raw object
644
+ # when using the RPC mode to solve dependencies
645
+ #
646
+ # Returns:
647
+ # - PackageDownloaded used to call genPackages
648
+ # - an array of permanent Package
649
+ # - an array of temporary Package
650
+ def get_dependencies_raw(opts={})
651
+ downloader, package_sha1 = check_version(opts)
652
+
653
+ # Check package server is up
654
+ downloader.checkRepo()
655
+
656
+ packages = []
657
+ temp_packages = []
658
+ packages, temp_packages = genPackages(downloader, opts)
659
+
660
+ packages.uniq!
661
+ temp_packages.uniq!
662
+ return downloader, packages, temp_packages
663
+ end
664
+
665
+ # Wrapper around get_dependencies_raw
666
+ #
667
+ # Calls get_dependencies_raw but convert the Package list to path to the packages
668
+ #
669
+ # Used by queryEnv to generate a list of URL to the package to download
670
+ # Returns:
671
+ # - PackageDownloaded used to call genPackages
672
+ # - an array of permanent package paths (String)
673
+ # - an array of temporary package paths (String)
674
+ def get_dependencies(opts = {})
675
+ downloader, packages, temp_packages = get_dependencies_raw(opts)
676
+ packages.each(){|p| downloader.getPackagePath(p)}
677
+ temp_packages.each(){|p| downloader.getPackagePath(p)}
678
+ return packages, temp_packages
679
+ end
680
+
681
+ # Download and extract the packages listed in packages AND temp_packages.
682
+ #
683
+ # temp_packages are extracted in a temporary dir.
684
+ # The dirpath will be stored in opts[:tempDir]
685
+ #
686
+ # The function returns an array of all the downloaded/extracted package that need to
687
+ # be install on the system. This is done by checking install package
688
+ # flags and DKMS options
689
+ def download_extract(downloader, opts, packages, temp_packages)
690
+ puts "Downloading and installing packages:"
691
+
692
+ # Check package server is up
693
+ downloader.checkRepo()
694
+
695
+ package_to_be_installed=[]
696
+
697
+ if WorkEnvs::settings[:db][:unthreaded] == false
698
+ semaphore = Mutex.new
699
+ threads = []
700
+ begin
701
+ processorCount = `cat /proc/cpuinfo 2> /dev/null | grep processor | wc -l`.chomp().to_i() / 2
702
+ processorCount = 1 if processorCount < 1
703
+ rescue
704
+ processorCount = 4
705
+ end
706
+ 1.upto(processorCount) {
707
+ threads << Thread.new {
708
+ while true do
709
+ pack = nil
710
+ semaphore.synchronize {
711
+ pack = packages.pop()
712
+ }
713
+ break if pack == nil
714
+ downloader.downloadAndExtract(pack, true, !pack.shouldKeep(opts))
715
+ if pack.shouldInstall(opts) then
716
+ semaphore.synchronize {
717
+ package_to_be_installed << pack
718
+ }
719
+ end
720
+
721
+ end
722
+ }
723
+ }
724
+ threads.each(){|thr| thr.join}
725
+ else
726
+ packages.each() {|pack|
727
+ downloader.downloadAndExtract(pack, true, !pack.shouldKeep(opts))
728
+ if pack.shouldInstall(opts) then
729
+ package_to_be_installed << pack
730
+ end
731
+ }
732
+ end
733
+
734
+ opts[:tempDir] = create_tmp_dir()
735
+ puts "Downloading and installing temporary packages (To be removed post-install):"
736
+ Dir.chdir(opts[:tempDir])
737
+ temp_packages.each() {|pack|
738
+ downloader.downloadAndExtract(pack, true, true)
739
+ }
740
+ Dir.chdir(opts[:dir])
741
+
742
+ return package_to_be_installed
743
+ end
744
+
745
+ # Cleanup and env
746
+ #
747
+ # Remove all files from the env and mark it as uninitialized.
748
+ # This also calls the cleanup method from the env Class tree.
749
+ def cleanup(opts)
750
+ path=genPath()
751
+ #Cleanup in order
752
+
753
+ WorkEnvs::familyTreeApply(self){|x|
754
+ next if x.singleton_methods().index(:cleanup) == nil
755
+ x.cleanup(opts)
756
+ }
757
+
758
+ #Deconfigure previous package to be sure
759
+ @versions.each(){|name, val|
760
+ @versions[name] = :uninitialized
761
+ }
762
+ @deps_infos = nil
763
+ @properties = {}
764
+ self.dump()
765
+
766
+ runCmd("chmod -R +w #{path}", !VERBOSE)
767
+ # Remove previous checkout
768
+ runCmd("rm -f #{path}/*.rpm #{path}/*.deb", !VERBOSE)
769
+ runCmd("rm -Rf #{path}/usr", !VERBOSE)
770
+ runCmd("rm -Rf #{path}/lib", !VERBOSE)
771
+ runCmd("rm -Rf #{path}/etc", !VERBOSE)
772
+ runCmd("rm -Rf #{path}/mppa", !VERBOSE)
773
+ runCmd("rm -Rf #{path}/*", !VERBOSE)
774
+ end
775
+
776
+ #
777
+ # Update configure and install a new envs
778
+ #
779
+ # Update phases are:
780
+ # - Generate package list
781
+ # - CLEANUP:
782
+ # - Cleanup per Env
783
+ # - Cleanup env globally (reset class and remove all files)
784
+ #
785
+ # - PRE_SETUP: Call pre_setup per Env
786
+ # - SETUP: Download and extract all packages
787
+ # - PRE_INSTALL: Call pre_install per Env
788
+ # - INSTALL: (yum install) all packages required (DKMS, etc.)
789
+ # - POST_SETUP:
790
+ # - Call post_setup per Env
791
+ # - Generate switch env script
792
+ # - Update the class and save to disk
793
+ def update(opts = {})
794
+ opts[:endDate] = DEFAULT_EXPIRATION_DATE if opts[:endDate] == nil
795
+ opts[:release] = @db_tables.join(":")
796
+
797
+
798
+ opts[:srcDir] = Dir.pwd()
799
+ opts[:self] = self
800
+
801
+ # Checkout a new set of packages in the environment workspace
802
+ path=genPath()
803
+ opts[:dir] = path
804
+
805
+ raise("Nothing to update") if (opts[:infos] == nil || opts[:infos].empty?) && !isDev?()
806
+ infos = opts[:infos]
807
+
808
+ if isDev?() then
809
+ puts "INFO: Updating a development environment"
810
+ end
811
+
812
+ downloader = get_downloader(opts)
813
+
814
+ puts "==========================================================="
815
+ puts "Update requested with these environments:"
816
+ puts infos.to_s
817
+ puts "==========================================================="
818
+ puts "Looking for their dependencies....."
819
+
820
+ prevOptions = @setup_options
821
+
822
+ @setup_options = opts[:envOpts]
823
+ #If we had hudson specs and no version for our current env, allow partial stuff
824
+ if infos.partialEnv != false && !infos.exists?(WorkEnvs::getEnvType(self))
825
+ @setup_options[WENV_OPTS_PARTIAL] = "true"
826
+ end
827
+ infos.partialEnv = true if @setup_options[WENV_OPTS_PARTIAL] == "true"
828
+
829
+ packages, temp_packages = genPackages(downloader, opts)
830
+
831
+ puts "==========================================================="
832
+ puts "Installing these requested environments (pulled by dependency):"
833
+ puts "Dependencies:\n"
834
+ puts infos.to_s
835
+ puts "==========================================================="
836
+
837
+ do_cleanup = true
838
+ do_setup = true
839
+ do_install = true
840
+
841
+
842
+ if !isDev?() && infos == @deps_infos && prevOptions == opts[:envOpts] &&
843
+ !(opts[:force_update] == true ||
844
+ (opts[:force_update_if_not_const] == true && opts[:envOpts][WENV_OPTS_CONST].to_s != "true"))
845
+ # Be lazy and check if we really need to update
846
+ # When true, this means that all the proper packages are already extracted. We just
847
+ # need to force the reinstallation of installPackages
848
+ do_cleanup = false
849
+ do_setup = false
850
+ end
851
+ if opts[:doNotUpdate] == true then
852
+ do_cleanup = false
853
+ do_setup = false
854
+ end
855
+
856
+ if do_cleanup then
857
+ # CLEANUP
858
+ cleanup(opts)
859
+ end
860
+
861
+ Dir.chdir(opts[:dir])
862
+ install_deps = nil
863
+ @deps_infos = YAMLLoad(infos.to_yaml())
864
+ @deps_infos.downloader = nil
865
+
866
+ # PRE SETUP
867
+ pre_setup(opts, packages, temp_packages)
868
+
869
+ # Save package list in opts.
870
+ opts[:packages] = []
871
+ (packages | temp_packages).each do |package|
872
+ opts[:packages].push package
873
+ end
874
+
875
+ # If we only want install packages, filter out all the unneeded ones
876
+ if !do_setup then
877
+ alt_pack = []
878
+
879
+ packages.each() {|pack|
880
+ alt_pack << pack if pack.shouldInstall(opts)
881
+ }
882
+ packages = alt_pack
883
+ temp_packages = []
884
+ end
885
+
886
+ # SETUP
887
+ install_deps, packages_to_install =
888
+ download_extract(downloader, opts, packages, temp_packages)
889
+
890
+
891
+ # INSTALL
892
+ if do_install && !packages_to_install.empty? then
893
+ pre_install(opts, packages, temp_packages)
894
+ downloader.installPackages(packages_to_install)
895
+ post_install(opts, packages, temp_packages)
896
+ end
897
+
898
+ # POST SETUP
899
+ if do_setup then
900
+ post_setup(opts)
901
+ end
902
+
903
+ Dir.chdir(opts[:srcDir])
904
+ runCmd("rm -Rf #{opts[:tempDir]}", true)
905
+
906
+ @expiration = opts[:endDate]
907
+
908
+ if do_setup then
909
+ @versions = getVersion()
910
+ end
911
+ @setup_options = opts[:envOpts]
912
+ @machine = downloader.arch[:label]
913
+ @table = downloader.table
914
+ # Dump the updated env at we're good to go
915
+ self.dump()
916
+
917
+ if @setup_options[WENV_OPTS_CONST].to_s() == "true"
918
+ Dir.chdir(opts[:dir])
919
+ runCmd("chmod -Rf -w #{path}/*; true", !VERBOSE)
920
+ end
921
+
922
+ return true
923
+ end
924
+
925
+ # Self migration method to update default values
926
+ def migrate()
927
+ case @version
928
+ when nil
929
+ # Very old pre rename Env have their own migrate function
930
+
931
+ #Compat to migrate old envs
932
+ if @machine == nil then
933
+ arch = WorkEnvs::getArch()
934
+ @machine = arch[:label]
935
+ end
936
+ if @release == nil
937
+ @release = false
938
+ end
939
+ if @setup_options == nil
940
+ @setup_options = WorkEnvs::EnvOpts.new()
941
+ end
942
+ @version = 1
943
+ when 1
944
+ @properties = {}
945
+ @version = 2
946
+ when 2
947
+ if @machine == nil then
948
+ arch = WorkEnvs::getArch()
949
+ @machine = arch[:label]
950
+ end
951
+ @version = 3
952
+ when 3
953
+ # New inheritance scheme. We cannot change our hierarchy but it should keep working anyway
954
+ @version = 4
955
+ when 4
956
+ # New switch env script
957
+ genSwitchEnvScript() if isSwitchable?() == true
958
+ @version = 5
959
+ when 5
960
+ case @release
961
+ when nil, false, "false"
962
+ @release = "package"
963
+ when true, "true"
964
+ @release = "releases"
965
+ end
966
+ @version = 6
967
+ when 6
968
+ @release = [ @release ]
969
+ @version = 7
970
+ when 7
971
+ @db_tables = @release
972
+ @version = 8
973
+ when 8
974
+ @ignore_conflicts = false
975
+ @version = 9
976
+ when 9
977
+ if @deps_infos then
978
+ infos = Dependencies.new
979
+
980
+ downloader = WorkEnvs::PackageDownloader.new(@machine, @db_tables, !VERBOSE)
981
+ infos.downloader = downloader
982
+
983
+ @deps_infos.each(){|s, v|
984
+ envClass = WorkEnvs::symbolToClass(s)
985
+ infos.push_version(envClass, v, false)
986
+ }
987
+ infos.downloader = nil
988
+ @deps_infos = infos
989
+ end
990
+ @version = 10
991
+ when WORK_ENV_VERSION
992
+ # End of recursion, we're up to date
993
+ return self
994
+ else
995
+ raise("Unknown version for env #{@name}")
996
+ end
997
+ # Save edit to the environment due to migration
998
+ self.dump()
999
+
1000
+ # Keep migrating until we reach the right version
1001
+ return self.migrate()
1002
+ end
1003
+ end
1004
+ end