right_link 5.9.2 → 5.9.5

Sign up to get free protection for your applications and to get access to all the features.
data/LICENSE CHANGED
@@ -1,4 +1,4 @@
1
- Copyright (c) 2009-2012 RightScale, Inc.
1
+ Copyright (c) 2009-2013 RightScale, Inc.
2
2
 
3
3
  Permission is hereby granted, free of charge, to any person obtaining
4
4
  a copy of this software and associated documentation files (the
data/README.rdoc CHANGED
@@ -10,6 +10,15 @@ to monitor the stdout and stderr streams of scripted processes. Servers running
10
10
  the RightLink agent configures themselves on startup an register with the
11
11
  mapper so that operational recipes and scripts can be run at a later time.
12
12
 
13
+ Supported cloud types:
14
+ * azure
15
+ * cloudstack
16
+ * ec2
17
+ * google
18
+ * openstack
19
+ * rackspace (Open Cloud)
20
+ * softlayer
21
+
13
22
  Refer to the wiki (https://github.com/rightscale/right_link/wikis) for up-to-date
14
23
  documentation.
15
24
 
@@ -82,7 +91,7 @@ See INSTALL.rdoc for more information
82
91
 
83
92
  <b>RightLink</b>
84
93
 
85
- Copyright:: Copyright (c) 2009-2011 RightScale, Inc.
94
+ Copyright:: Copyright (c) 2009-2013 RightScale, Inc.
86
95
 
87
96
  Permission is hereby granted, free of charge, to any person obtaining
88
97
  a copy of this software and associated documentation files (the
data/RELEASES.rdoc CHANGED
@@ -1,3 +1,50 @@
1
+ = 5.9.5 (RightLink 5.9 release candidate 3)
2
+
3
+ Released 2013-11-27
4
+
5
+ == Bug Fixes
6
+
7
+ * Added ruby as a package dependency.
8
+ * Removed unnecessary warning about rightscale user during package install.
9
+ * Fixed regression in patching mechanism.
10
+
11
+ = 5.9.4 (RightLink 5.9 release candidate 2)
12
+
13
+ Released 2013-11-20
14
+
15
+ == Changes to Existing Functionality
16
+
17
+ * Revert rs_tag query TAG_LIST param to split tags by space. No spaces allowed in a tag.
18
+
19
+ == Bug Fixes
20
+
21
+ * Redundant rs_state output when executed.
22
+ * Fix RightLink failure to run RightScripts that contain a single or double quote.
23
+
24
+ = 5.9.3 (RightLink 5.9 release candidate 1)
25
+
26
+ Released 2013-10-17
27
+
28
+ == New Features
29
+ * rs_state utility that lets users interrogate the run-state (rs_state --type run) or
30
+ agent state (rs_state --type agent)
31
+
32
+ == Changes to Existing Functionality
33
+
34
+ * RightLink changes the audit summary when waiting for missing inputs on boot. It also strands if
35
+ inputs are still missing after 45 minutes. ("Missing" means inputs that are set to a value that
36
+ cannot yet be computed, such as the IP address of a non-running server.)
37
+ * RS_DECOM_REASON is not populated anymore
38
+ * The cloud-support package for Rackspace Classic cloud suffixed as "rackspace-first-gen" has been removed.
39
+ * The cloud-support package for Rackspace OpenCloud is suffixed with "rackspace".
40
+
41
+ == Bug Fixes
42
+
43
+ * RightLink sets correct permissions on users' home directories (using OS defaults)
44
+ for users that have been created on login.
45
+ * RightLink CLI tools will no longer crash when executed without arguments.
46
+ * RightLink will no longer give a warning about missing tty name when boot scripts complete.
47
+
1
48
  = 5.9.2 (RightLink 5.9 beta 3)
2
49
 
3
50
  Released 2013-09-06.
@@ -118,7 +165,7 @@ Released 2013-07-13.
118
165
 
119
166
  == Bug Fixes
120
167
 
121
- * Managed login always displays MOTD, works with older versions of sudo,
168
+ * Managed login always displays MOTD, works with older versions of sudo,
122
169
  * Cookbook download is more reliable in fail-and-retry scenarios
123
170
 
124
171
  = 5.8.8 (General Availability release in conjunction with ServerTemplates v12.11 LTS)
@@ -51,6 +51,9 @@ class InstanceSetup
51
51
  # Maximum time between nag audits for missing inputs.
52
52
  MISSING_INPUT_AUDIT_DELAY_SECS = 2 * 60
53
53
 
54
+ # Maximum time that instances will wait for missing inputs before stranding
55
+ MISSING_INPUT_TIMEOUT = 45 * 60
56
+
54
57
  # Tag set on instances that are part of an array
55
58
  AUTO_LAUNCH_TAG ='rs_launch:type=auto'
56
59
 
@@ -135,9 +138,10 @@ class InstanceSetup
135
138
  # true:: Always return true
136
139
  def init_boot
137
140
  RightScale::Sender.instance.initialize_offline_queue
138
- payload = {:agent_identity => @agent_identity,
139
- :r_s_version => RightScale::AgentConfig.protocol_version,
140
- :resource_uid => RightScale::InstanceState.resource_uid}
141
+ payload = {:agent_identity => @agent_identity,
142
+ :right_link_version => RightLink.version,
143
+ :r_s_version => RightScale::AgentConfig.protocol_version,
144
+ :resource_uid => RightScale::InstanceState.resource_uid}
141
145
  req = RightScale::IdempotentRequest.new('/booter/declare', payload, :retry_on_error => true)
142
146
  req.callback do |res|
143
147
  RightScale::Sender.instance.start_offline_queue
@@ -403,6 +407,7 @@ class InstanceSetup
403
407
 
404
408
  req.callback do |bundle|
405
409
  if bundle.executables.any? { |e| !e.ready }
410
+ @audit.create_new_section("Waiting for missing inputs")
406
411
  retrieve_missing_inputs(bundle) { cb.call(success_result(bundle)) }
407
412
  else
408
413
  yield success_result(bundle)
@@ -461,7 +466,7 @@ class InstanceSetup
461
466
  yield
462
467
  else
463
468
  # keep state to provide fewer but more meaningful audits.
464
- last_missing_inputs ||= {}
469
+ last_missing_inputs ||= {:started_at => Time.now}
465
470
  last_missing_inputs[:executables] ||= {}
466
471
 
467
472
  # don't need to audit on each attempt to resolve missing inputs, but nag
@@ -477,9 +482,11 @@ class InstanceSetup
477
482
  missing_input_names = []
478
483
 
479
484
  e.input_flags.each {|k,v| missing_input_names << k if v.member?("unready")}
480
- @audit.append_info("Waiting for the following missing inputs which are used by '#{e.nickname}': #{missing_input_names.join(", ")}")
485
+ if audit_missing_inputs
486
+ @audit.append_info("Waiting for the following missing inputs which are used by '#{e.nickname}': #{missing_input_names.join(", ")}")
487
+ sent_audit = true
488
+ end
481
489
 
482
- sent_audit = true
483
490
  missing_inputs_executables[e.nickname] = missing_input_names
484
491
  end
485
492
 
@@ -488,14 +495,17 @@ class InstanceSetup
488
495
  unless missing_inputs_executables[nickname]
489
496
  title = RightScale::RightScriptsCookbook.recipe_title(nickname)
490
497
  @audit.append_info("The inputs used by #{title} which had been missing have now been resolved.")
491
- sent_audit = true
492
498
  end
493
499
  end
494
500
  last_missing_inputs[:executables] = missing_inputs_executables
495
501
  last_missing_inputs[:last_audit_time] = Time.now if sent_audit
496
502
 
497
- # schedule retry to retrieve missing inputs.
498
- EM.add_timer(MISSING_INPUT_RETRY_DELAY_SECS) { retrieve_missing_inputs(bundle, last_missing_inputs, &cb) }
503
+ if Time.now - last_missing_inputs[:started_at] < MISSING_INPUT_TIMEOUT
504
+ # schedule retry to retrieve missing inputs.
505
+ EM.add_timer(MISSING_INPUT_RETRY_DELAY_SECS) { retrieve_missing_inputs(bundle, last_missing_inputs, &cb) }
506
+ else
507
+ strand("Failed to retrieve missing inputs after #{MISSING_INPUT_TIMEOUT / 60} minutes")
508
+ end
499
509
  end
500
510
  end
501
511
 
data/bin/rs_state ADDED
@@ -0,0 +1,31 @@
1
+ # Copyright (c) 2013 RightScale Inc
2
+ #
3
+ # Permission is hereby granted, free of charge, to any person obtaining
4
+ # a copy of this software and associated documentation files (the
5
+ # "Software"), to deal in the Software without restriction, including
6
+ # without limitation the rights to use, copy, modify, merge, publish,
7
+ # distribute, sublicense, and/or sell copies of the Software, and to
8
+ # permit persons to whom the Software is furnished to do so, subject to
9
+ # the following conditions:
10
+ #
11
+ # The above copyright notice and this permission notice shall be
12
+ # included in all copies or substantial portions of the Software.
13
+ #
14
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21
+
22
+ # rs_state --help for usage information
23
+ #
24
+ # See scripts/state_controller.rb for additional information.
25
+
26
+ require File.expand_path(File.join(File.dirname(__FILE__), '..', 'lib', 'gem_dependencies'))
27
+
28
+ $:.push(File.join(File.dirname(__FILE__), '..', 'scripts'))
29
+ require 'state_controller'
30
+
31
+ RightScale::RightLinkStateController::run
@@ -59,7 +59,7 @@ class Chef
59
59
  # true:: Always return true
60
60
  def action_load
61
61
  tags = RightScale::Cook.instance.load_tags(@new_resource.timeout)
62
- node[:right_link_tags] = tags
62
+ node.set[:right_link_tags] = tags
63
63
  true
64
64
  end
65
65
 
@@ -46,13 +46,13 @@ class Chef
46
46
  # === Return
47
47
  # true:: Always return true
48
48
  def action_load
49
- node[:server_collection] ||= {}
50
- node[:server_collection][@new_resource.name] = {}
49
+ node.set[:server_collection] ||= {}
50
+ node.set[:server_collection][@new_resource.name] = {}
51
51
  return unless @new_resource.tags && !@new_resource.tags.empty?
52
52
 
53
53
  result = RightScale::Cook.instance.query_tags(@new_resource.tags, @new_resource.agent_ids, @new_resource.timeout)
54
54
  collection = result.inject({}) { |res, (k, v)| res[k] = v['tags']; res }
55
- node[:server_collection][@new_resource.name] = collection
55
+ node.set[:server_collection][@new_resource.name] = collection
56
56
  true
57
57
  end
58
58
 
@@ -45,7 +45,7 @@ end
45
45
 
46
46
  # defaults.
47
47
  default_option([:user_metadata, :metadata_tree_climber, :create_leaf_override], method(:create_user_metadata_leaf))
48
- default_option([:metadata_source, :user_metadata_source_file_path], File.join(RightScale::Platform.filesystem.spool_dir, name.to_s, 'user-data.txt'))
48
+ default_option([:metadata_source, :user_metadata_source_file_path], File.join(RightScale::Platform.filesystem.spool_dir, 'rackspace', 'user-data.txt'))
49
49
 
50
50
  # Determines if the current instance is running on rackspace.
51
51
  #
@@ -74,5 +74,14 @@ def update_details
74
74
  details[:private_ip] = ::RightScale::CloudUtilities.ip_for_interface(ohai, :eth1)
75
75
  end
76
76
  end
77
+
78
+ # rack_connect (and managed?) instances may not have network interfaces for
79
+ # public ip, so attempt the "what's my ip?" method in these cases.
80
+ unless details[:public_ip]
81
+ if public_ip = ::RightScale::CloudUtilities.query_whats_my_ip(:logger=>logger)
82
+ details[:public_ip] = public_ip
83
+ end
84
+ end
85
+
77
86
  return details
78
87
  end
@@ -29,7 +29,10 @@ module RightScale
29
29
 
30
30
  # Path to RightScale files in parent directory of right_link
31
31
  def self.parent_dir
32
- File.dirname(File.normalize_path(File.join(File.dirname(__FILE__), '..', '..', '..', 'right_link')))
32
+ # NOTE:
33
+ # RightScale is missing right_link home dir for Linux Platform
34
+ # will be fixed for v6.0, - ticket #16947
35
+ RightScale::Platform.windows? ? RightScale::Platform.filesystem.right_link_home_dir : '/opt/rightscale'
33
36
  end
34
37
 
35
38
  # @return [Array] an appropriate sequence of root directories for configuring the RightLink agent
@@ -112,7 +112,7 @@ module RightScale
112
112
  # Previously we accessed RestClient directly and used it's wrapper method to instantiate
113
113
  # a RestClient::Request object. This wrapper was not passing all options down the stack
114
114
  # so now we invoke the RestClient::Request object directly, passing it our desired options
115
- client.execute(:method => :get, :url => "https://#{endpoint}:443#{resource}", :timeout => calculate_timeout(attempts), :verify_ssl => OpenSSL::SSL::VERIFY_PEER, :ssl_ca_file => get_ca_file, :headers => {:user_agent => "RightLink v#{AgentConfig.protocol_version}", 'X-RightLink-Version' => RightLink::VERSION }) do |response, request, result|
115
+ client.execute(:method => :get, :url => "https://#{endpoint}:443#{resource}", :timeout => calculate_timeout(attempts), :verify_ssl => OpenSSL::SSL::VERIFY_PEER, :ssl_ca_file => get_ca_file, :headers => {:user_agent => "RightLink v#{AgentConfig.protocol_version}", 'X-RightLink-Version' => RightLink.version }) do |response, request, result|
116
116
  if result.kind_of?(Net::HTTPSuccess)
117
117
  @size = result.content_length || response.size || 0
118
118
  @speed = @size / (Time.now - t0)
@@ -149,9 +149,6 @@ module RightScale
149
149
  ::RightScale::OptionsBag::OPTIONS_ENV =>
150
150
  ::ENV[::RightScale::OptionsBag::OPTIONS_ENV]
151
151
  }
152
- if @context.decommission?
153
- environment['RS_DECOM_REASON'] = @context.decommission_type
154
- end
155
152
 
156
153
  # spawn
157
154
  RightScale::RightPopen.popen3_async(
@@ -32,10 +32,10 @@ module RightScale
32
32
 
33
33
  CONFIG=\
34
34
  if File.exists?(CONFIG_YAML_FILE)
35
- RightSupport::Config.features(CONFIG_YAML_FILE)
35
+ RightSupport::Config.features(CONFIG_YAML_FILE)
36
36
  else
37
37
  RightSupport::Config.features({})
38
- end
38
+ end
39
39
 
40
40
  # States that are recorded in a standard fashion and audited when transitioned to
41
41
  RECORDED_STATES = %w{ booting operational stranded decommissioning }
@@ -120,7 +120,7 @@ module RightScale
120
120
 
121
121
  # (String) Type of decommission currently in progress or nil
122
122
  def self.decommission_type
123
- if @value == 'decommissioning'
123
+ if @value == 'decommissioning' || @value == 'decommissioned'
124
124
  @decommission_type
125
125
  else
126
126
  raise RightScale::Exceptions::WrongState.new("Unexpected call to InstanceState.decommission_type for current state #{@value.inspect}")
@@ -175,7 +175,7 @@ module RightScale
175
175
  # 3) bundled boot -- Agent already ran; agent ID changed: transition back to booting
176
176
  # 4) decommission/crash -- Agent exited anyway; ID not changed; no reboot; keep old state entirely
177
177
  # 5) ec2 restart -- Agent already ran; agent ID changed; instance ID is the same; transition back to booting
178
- if state['identity'] && state['identity'] != identity
178
+ if state['identity'] && state['identity'] != identity && !@read_only
179
179
  @last_recorded_value = state['last_recorded_value']
180
180
  self.value = 'booting'
181
181
  # if the current resource_uid is the same as the last
@@ -190,7 +190,7 @@ module RightScale
190
190
  # CASE 3 -- identity has changed; bundled boot
191
191
  Log.debug("Bundle detected; transitioning state to booting")
192
192
  end
193
- elsif state['reboot']
193
+ elsif state['reboot'] && !@read_only
194
194
  # CASE 2 -- rebooting flagged by rightboot script in linux or by shutdown notification in windows
195
195
  Log.debug("Reboot detected; transitioning state to booting")
196
196
  @last_recorded_value = state['last_recorded_value']
@@ -199,11 +199,12 @@ module RightScale
199
199
  else
200
200
  # CASE 4 -- restart without reboot; continue with retries if recorded state does not match
201
201
  @value = state['value']
202
+ @reboot = state['reboot']
202
203
  @startup_tags = state['startup_tags']
203
204
  @log_level = state['log_level']
204
205
  @last_recorded_value = state['last_recorded_value']
205
206
  @record_retries = state['record_retries']
206
- @decommission_type = state['decommission_type'] if @value == 'decommissioning'
207
+ @decommission_type = state['decommission_type'] if (@value == 'decommissioning' || @value == 'decommissioned')
207
208
  if @value != @last_recorded_value && RECORDED_STATES.include?(@value) &&
208
209
  @record_retries < MAX_RECORD_STATE_RETRIES && !@read_only
209
210
  record_state
@@ -251,7 +252,7 @@ module RightScale
251
252
  Log.info("Transitioning state from #{previous_val} to #{val}")
252
253
  @reboot = false if val != :booting
253
254
  @value = val
254
- @decommission_type = nil unless @value == 'decommissioning'
255
+ @decommission_type = nil unless (@value == 'decommissioning' || @value == 'decommissioned')
255
256
 
256
257
  update_logger
257
258
  update_motd
@@ -270,7 +271,7 @@ module RightScale
270
271
  #
271
272
  # === Return
272
273
  # result(String):: new decommission type
273
- #
274
+ #
274
275
  # === Raise
275
276
  # RightScale::Exceptions::Application:: Cannot update in read-only mod
276
277
  def self.decommission_type=(decommission_type)
@@ -510,9 +511,9 @@ module RightScale
510
511
  return unless RightScale::Platform.linux?
511
512
 
512
513
  if SUCCESSFUL_STATES.include?(@value)
513
- system('echo "RightScale installation complete. Details can be found in system logs." | wall') rescue nil
514
+ system('echo "RightScale installation complete. Details can be found in system logs." | wall > /dev/null 2>&1') rescue nil
514
515
  elsif FAILED_STATES.include?(@value)
515
- system('echo "RightScale installation failed. Please review system logs." | wall') rescue nil
516
+ system('echo "RightScale installation failed. Please review system logs." | wall > /dev/null 2>&1') rescue nil
516
517
  end
517
518
 
518
519
  return nil
@@ -537,7 +538,7 @@ module RightScale
537
538
  'last_observed_resource_uid' => @resource_uid}
538
539
 
539
540
  # Only include deommission_type when decommissioning
540
- state_to_store['decommission_type'] = @decommission_type if @value == 'decommissioning'
541
+ state_to_store['decommission_type'] = @decommission_type if (@value == 'decommissioning' || @value == 'decommissioned')
541
542
 
542
543
  RightScale::JsonUtilities::write_json(STATE_FILE, state_to_store)
543
544
  true
@@ -190,7 +190,6 @@ module RightScale
190
190
  when 0
191
191
  home_dir = Shellwords.escape(Etc.getpwnam(username).dir)
192
192
 
193
- sudo("chmod 0771 #{Shellwords.escape(home_dir)}")
194
193
  # Locking account to prevent warning os SUSE(it complains on unlocking non-locked account)
195
194
  modify_user(username, true, shell)
196
195
 
@@ -346,33 +345,6 @@ module RightScale
346
345
  groups.include?(name)
347
346
  end
348
347
 
349
- def setup_profile(username, home_dir, custom_data, force)
350
- return false if custom_data.nil? || custom_data.empty?
351
-
352
- checksum_path = File.join('.rightscale', PROFILE_CHECKSUM)
353
- return false if !force && File.exists?(File.join(home_dir, checksum_path))
354
-
355
- t0 = Time.now.to_i
356
- yield("Performing profile setup for #{username}...") if block_given?
357
-
358
- tmpdir = Dir.mktmpdir
359
- file_path = File.join(tmpdir, File.basename(custom_data))
360
- if download_files(custom_data, file_path) && extract_files(username, file_path, home_dir)
361
- save_checksum(username, file_path, checksum_path, home_dir)
362
- t1 = Time.now.to_i
363
- yield("Setup complete (#{t1 - t0} sec)") if block_given? && (t1 - t0 >= 2)
364
- end
365
-
366
- return true
367
- rescue Exception => e
368
- yield("Failed to create profile for #{username}; continuing") if block_given?
369
- yield("#{e.class.name}: #{e.message} - #{e.backtrace.first}") if block_given?
370
- Log.error("#{e.class.name}: #{e.message} - #{e.backtrace.first}")
371
- return false
372
- ensure
373
- FileUtils.rm_rf(tmpdir) if tmpdir && File.exists?(tmpdir)
374
- end
375
-
376
348
  # Set some of the environment variables that would normally be set if a user
377
349
  # were to login to an interactive shell. This is useful when simulating an
378
350
  # interactive login, e.g. for purposes of running a user-specified command
@@ -432,100 +404,5 @@ module RightScale
432
404
 
433
405
  File.join(path, cmd)
434
406
  end
435
-
436
- # Downloads a file from specified URL
437
- #
438
- # === Parameters
439
- # url(String):: URL to file
440
- # path(String):: downloaded file path
441
- #
442
- # === Return
443
- # downloaded(Boolean):: true if downloaded and saved successfully
444
- def download_files(url, path)
445
- client = RightSupport::Net::HTTPClient.new
446
- response = client.get(url, :timeout => 10)
447
- File.open(path, "wb") { |file| file.write(response) } unless response.empty?
448
- File.exists?(path)
449
- rescue Exception => e
450
- Log.error("#{e.class.name}: #{e.message} - #{e.backtrace.first}")
451
- false
452
- end
453
-
454
- # Extracts an archive and moves files to destination directory
455
- # Supported archive types are:
456
- # .tar.bz2 / .tbz
457
- # .tar.gz / .tgz
458
- # .zip
459
- #
460
- # === Parameters
461
- # username(String):: account's username
462
- # filename(String):: archive's path
463
- # destination_path(String):: path where extracted files should be
464
- # moved
465
- #
466
- # === Return
467
- # extracted(Boolean):: true if archive is extracted successfully
468
- def extract_files(username, filename, destination_path)
469
- escaped_filename = Shellwords.escape(filename)
470
-
471
- case filename
472
- when /(?:\.tar\.bz2|\.tbz)$/
473
- result = sudo("tar jxf #{escaped_filename} -C #{destination_path}")
474
- when /(?:\.tar\.gz|\.tgz)$/
475
- result = sudo("tar zxf #{escaped_filename} -C #{destination_path}")
476
- when /\.zip$/
477
- result = sudo("unzip -o #{escaped_filename} -d #{destination_path}")
478
- else
479
- raise ArgumentError, "Don't know how to extract #{filename}'"
480
- end
481
-
482
- extracted = result.success?
483
- chowned = change_owner(username, username, destination_path)
484
-
485
- extracted && chowned
486
- end
487
-
488
- # Calculates MD5 checksum for specified file and saves it
489
- #
490
- # === Parameters
491
- # username(String):: account's username
492
- # target(String):: path to file
493
- # checksum_path(String):: relative path to checksum file
494
- # destination(String):: path to file where checksum should be saved
495
- #
496
- # === Return
497
- # nil
498
- def save_checksum(username, target, checksum_path, destination)
499
- checksum = Digest::MD5.file(target).to_s
500
-
501
- temp_dir = File.join(File.dirname(target), File.dirname(checksum_path))
502
- temp_path = File.join(File.dirname(target), checksum_path)
503
-
504
- FileUtils.mkdir_p(temp_dir)
505
- FileUtils.chmod_R(0771, temp_dir) # need +x to others for File.exists? => true
506
- File.open(temp_path, "w") { |f| f.write(checksum) }
507
-
508
- change_owner(username, username, temp_dir)
509
- sudo("mv #{temp_dir} #{destination}")
510
- rescue Exception => e
511
- STDERR.puts "Failed to save checksum for #{username} profile"
512
- STDERR.puts "#{e.class.name}: #{e.message} - #{e.backtrace.first}"
513
- Log.error("#{e.class.name}: #{e.message} - #{e.backtrace.first}")
514
- end
515
-
516
- # Changes owner of directories and files from given path
517
- #
518
- # === Parameters
519
- # username(String):: desired owner's username
520
- # group(String):: desired group name
521
- # path(String):: path for owner changing
522
- #
523
- # === Return
524
- # chowned(Boolean):: true if owner changed successfully
525
- def change_owner(username, group, path)
526
- result = sudo("chown -R #{Shellwords.escape(username)}:#{Shellwords.escape(group)} #{path}")
527
-
528
- result.success?
529
- end
530
407
  end
531
408
  end