souffle 0.0.1 → 0.0.2

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.
Files changed (49) hide show
  1. data/Gemfile +9 -3
  2. data/README.md +6 -0
  3. data/bin/{souffle-server → souffle} +0 -0
  4. data/lib/souffle.rb +8 -8
  5. data/lib/souffle/application.rb +15 -10
  6. data/lib/souffle/application/souffle-server.rb +90 -5
  7. data/lib/souffle/config.rb +88 -59
  8. data/lib/souffle/daemon.rb +156 -0
  9. data/lib/souffle/exceptions.rb +29 -17
  10. data/lib/souffle/http.rb +43 -0
  11. data/lib/souffle/log.rb +11 -14
  12. data/lib/souffle/node.rb +91 -53
  13. data/lib/souffle/node/runlist.rb +16 -18
  14. data/lib/souffle/node/runlist_item.rb +43 -36
  15. data/lib/souffle/node/runlist_parser.rb +60 -62
  16. data/lib/souffle/polling_event.rb +110 -0
  17. data/lib/souffle/provider.rb +231 -23
  18. data/lib/souffle/provider/aws.rb +654 -7
  19. data/lib/souffle/provider/vagrant.rb +42 -5
  20. data/lib/souffle/provisioner.rb +55 -0
  21. data/lib/souffle/provisioner/node.rb +157 -0
  22. data/lib/souffle/provisioner/system.rb +195 -0
  23. data/lib/souffle/redis_client.rb +8 -0
  24. data/lib/souffle/redis_mixin.rb +40 -0
  25. data/lib/souffle/server.rb +42 -8
  26. data/lib/souffle/ssh_monkey.rb +8 -0
  27. data/lib/souffle/state.rb +16 -0
  28. data/lib/souffle/system.rb +139 -37
  29. data/lib/souffle/template.rb +30 -0
  30. data/lib/souffle/templates/Vagrantfile.erb +41 -0
  31. data/lib/souffle/version.rb +6 -0
  32. data/spec/config_spec.rb +20 -0
  33. data/spec/log_spec.rb +24 -0
  34. data/spec/{runlist_parser_spec.rb → node/runlist_parser_spec.rb} +1 -1
  35. data/spec/{runlist_spec.rb → node/runlist_spec.rb} +1 -1
  36. data/spec/node_spec.rb +43 -8
  37. data/spec/provider_spec.rb +56 -0
  38. data/spec/providers/aws_provider_spec.rb +114 -0
  39. data/spec/providers/vagrant_provider_spec.rb +22 -0
  40. data/spec/provisioner_spec.rb +47 -0
  41. data/spec/spec_helper.rb +8 -0
  42. data/spec/system_spec.rb +242 -13
  43. data/spec/template_spec.rb +20 -0
  44. data/spec/templates/example_template.erb +1 -0
  45. metadata +125 -30
  46. data/bin/souffle-worker +0 -7
  47. data/lib/souffle/application/souffle-worker.rb +0 -46
  48. data/lib/souffle/providers.rb +0 -2
  49. data/lib/souffle/worker.rb +0 -14
data/Gemfile CHANGED
@@ -7,21 +7,27 @@ gem "yajl-ruby", "~> 1.1.0"
7
7
  gem "eventmachine", "~> 0.12.10"
8
8
  gem "amqp", "~> 0.9.7"
9
9
  gem "state_machine", "~> 1.1.2"
10
+ gem "em-ssh", "~> 0.4.0"
11
+ gem "em-synchrony", "~> 0.2.0"
10
12
 
11
13
  gem "right_aws", "~> 3.0.4"
14
+ gem "tilt", "~> 1.3.3"
15
+ gem "redis", "~> 3.0.1"
12
16
 
13
- gem "mixlib-cli"
17
+ gem "mixlib-cli", ">= 1.2.2"
14
18
  gem "mixlib-config", ">= 1.1.0"
15
19
  gem "mixlib-log", ">= 1.3.0"
16
20
 
21
+ gem "thin", "~> 1.4.1"
22
+ gem "rack", "~> 1.4.1"
23
+ gem "sinatra", "~> 1.3.2"
24
+
17
25
  # Add dependencies to develop your gem here.
18
26
  # Include everything needed to run rake, tests, features, etc.
19
27
  group :development do
20
28
  gem "rspec", "~> 2.10.0"
21
- gem "fakefs", "~> 0.4.0"
22
29
  gem "yard", "~> 0.8"
23
30
  gem "redcarpet", "~> 2.1.1"
24
- gem "rdoc", "~> 3.12"
25
31
  gem "cucumber", ">= 0"
26
32
  gem "bundler", "~> 1.1.0"
27
33
  gem "jeweler", "~> 1.8.3"
data/README.md CHANGED
@@ -2,6 +2,12 @@
2
2
 
3
3
  An orchestrator for setting up isolated chef-managed systems.
4
4
 
5
+ ## A note on tests
6
+
7
+ In order to avoid painfully silly charges and costs, all of the AWS tests
8
+ that require you to pay (spinning up machines, etc), will only run if you
9
+ have the environment variable `AWS_LIVE` set to `true`.
10
+
5
11
  ## Contributing to souffle
6
12
 
7
13
  * Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet.
File without changes
data/lib/souffle.rb CHANGED
@@ -1,17 +1,17 @@
1
1
  $:.unshift File.dirname(__FILE__)
2
2
  require 'yajl'
3
+ require 'yajl/json_gem'
3
4
  require 'eventmachine'
4
- require 'state_machine'
5
- require 'right_aws'
6
-
7
- # An orchestrator for setting up isolated chef-managed systems.
8
- module Souffle
9
- VERSION = "0.0.1"
10
- end
5
+ require 'em-ssh'
6
+ require 'em-synchrony'
11
7
 
8
+ require 'souffle/version'
9
+ require 'souffle/ssh_monkey'
12
10
  require 'souffle/log'
13
11
  require 'souffle/exceptions'
14
12
  require 'souffle/config'
15
- require 'souffle/providers'
13
+ require 'souffle/daemon'
16
14
  require 'souffle/node'
17
15
  require 'souffle/system'
16
+ require 'souffle/provider'
17
+ require 'souffle/provisioner'
@@ -4,11 +4,15 @@ require 'mixlib/cli'
4
4
  class Souffle::Application
5
5
  include Mixlib::CLI
6
6
 
7
+ # The commands that were left unparsed from parse_options.
8
+ attr_accessor :commands
9
+
7
10
  # Added a Wakeup exception.
8
11
  class Wakeup < Exception; end
9
12
 
10
13
  # Initialize the application, setting up default handlers.
11
14
  def initialize
15
+ @commands = []
12
16
  super
13
17
 
14
18
  trap("TERM") do
@@ -42,10 +46,11 @@ class Souffle::Application
42
46
  begin
43
47
  ::File.open(config[:config_file]) { |f| apply_config(f.path) }
44
48
  rescue Errno::ENOENT => error
45
- noconfig = "Did not find config file: #{config[:config_file]}"
46
- Souffle::Log.warn("*****************************************")
47
- Souffle::Log.warn("#{noconfig}, using command line options.")
48
- Souffle::Log.warn("*****************************************")
49
+ msg = "Did not find the config file: #{config[:config_file]}"
50
+ msg << ", Using command line options."
51
+ Souffle::Log.warn "*****************************************"
52
+ Souffle::Log.warn msg
53
+ Souffle::Log.warn "*****************************************"
49
54
  end
50
55
  end
51
56
 
@@ -56,9 +61,9 @@ class Souffle::Application
56
61
  Souffle::Log.init(Souffle::Config[:log_location])
57
62
  if ( Souffle::Config[:log_location] != STDOUT ) && STDOUT.tty? &&
58
63
  ( !Souffle::Config[:daemonize] )
59
- stdout_loger = Logger.new(STDOUT)
64
+ stdout_logger = Logger.new(STDOUT)
60
65
  STDOUT.sync = true
61
- stdout_logger = Souffle::Log.logger.formatter
66
+ stdout_logger.formatter = Souffle::Log.logger.formatter
62
67
  Souffle::Log.loggers << stdout_logger
63
68
  end
64
69
  Souffle::Log.level = Souffle::Config[:log_level]
@@ -113,8 +118,8 @@ class Souffle::Application
113
118
  # Log a fatal error message to both STDERR and the Logger,
114
119
  # exit the application with a fatal message.
115
120
  #
116
- # @param [ msg ] String The message to log.
117
- # @param [ err ] Integer The exit level.
121
+ # @param [ String ] msg The message to log.
122
+ # @param [ Fixnum ] err The exit level.
118
123
  def fatal!(msg, err = -1)
119
124
  Souffle::Log.fatal(msg)
120
125
  Process.exit err
@@ -123,8 +128,8 @@ class Souffle::Application
123
128
  # Log a fatal error message to both STDERR and the Logger,
124
129
  # exit the application with a debug message.
125
130
  #
126
- # @param [ msg ] String The message to log.
127
- # @param [ err ] Integer The exit level.
131
+ # @param [ String ] msg The message to log.
132
+ # @param [ Fixnum ] err The exit level.
128
133
  def exit!(msg, err = -1)
129
134
  Souffle::Log.debug(msg)
130
135
  Process.exit err
@@ -7,7 +7,7 @@ class Souffle::Application::Server < Souffle::Application
7
7
  option :config_file,
8
8
  :short => "-c CONFIG",
9
9
  :long => "--config CONFIG",
10
- :default => "/etc/souffle/worker.rb",
10
+ :default => "/etc/souffle/souffle.rb",
11
11
  :description => "The configuration file to use"
12
12
 
13
13
  option :log_level,
@@ -17,10 +17,78 @@ class Souffle::Application::Server < Souffle::Application
17
17
  :proc => lambda { |l| l.to_sym }
18
18
 
19
19
  option :log_location,
20
- :short => "-L LOGLOCATION",
21
- :long => "--logfile LOGLOCATION",
22
- :description => "Set the log file location, defaults to STDOUT",
23
- :proc => nil
20
+ :short => "-L LOG_LOCATION",
21
+ :long => "--logfile LOG_LOCATION",
22
+ :description => "Set the log file location, defaults to STDOUT",
23
+ :proc => nil
24
+
25
+ option :user,
26
+ :short => "-u USER",
27
+ :long => "--user USER",
28
+ :description => "User to set privilege to",
29
+ :proc => nil
30
+
31
+ option :group,
32
+ :short => "-g GROUP",
33
+ :long => "--group GROUP",
34
+ :description => "Group to set privilege to",
35
+ :proc => nil
36
+
37
+ option :daemonize,
38
+ :short => "-d",
39
+ :long => "--daemonize",
40
+ :default => false,
41
+ :description => "Run the application as a daemon (forces `-s`)",
42
+ :proc => lambda { |p| true }
43
+
44
+ option :environment,
45
+ :short => "-E",
46
+ :long => "--environment",
47
+ :description => "The environment profile to use",
48
+ :proc => nil
49
+
50
+ option :rack_host,
51
+ :short => "-H HOSTNAME",
52
+ :long => "--hostname HOSTNAME",
53
+ :description => "Hostname to listen on (default: 0.0.0.0)",
54
+ :proc => nil
55
+
56
+ option :rack_port,
57
+ :short => "-P PORT",
58
+ :long => "--port PORT",
59
+ :description => "Port to listen on (default: 8080)",
60
+ :proc => lambda { |p| p.to_i }
61
+
62
+ option :vagrant_dir,
63
+ :short => "-V VAGRANT_DIR",
64
+ :long => "--vagrant_dir VAGRANT_DIR",
65
+ :description => "The path to the base vagrant vm directory",
66
+ :proc => nil
67
+
68
+ option :pid_file,
69
+ :short => "-f PID_FILE",
70
+ :long => "--pid PID_FILE",
71
+ :description => "Set the PID file location, defaults to /tmp/souffle.pid",
72
+ :proc => nil
73
+
74
+ option :provider,
75
+ :short => "-p PROVIDER",
76
+ :long => "--provider PROVIDER",
77
+ :description => "The provider to use (overrides config)",
78
+ :proc => nil
79
+
80
+ option :json,
81
+ :short => "-j JSON",
82
+ :long => "--json JSON",
83
+ :description => "The json for a single provision (negates `-s`)",
84
+ :proc => nil
85
+
86
+ option :server,
87
+ :short => "-s",
88
+ :long => "--server",
89
+ :default => false,
90
+ :description => "Start the application as a server",
91
+ :proc => nil
24
92
 
25
93
  option :help,
26
94
  :short => "-h",
@@ -39,8 +107,25 @@ class Souffle::Application::Server < Souffle::Application
39
107
  :proc => lambda { |v| puts "Souffle: #{::Souffle::VERSION}"},
40
108
  :exit => 0
41
109
 
110
+ # Grabs all of the cli parameters and generates the mixlib config object.
42
111
  def initialize
43
112
  super
113
+ Souffle::Config.merge!(config)
114
+ end
115
+
116
+ # Configures the souffle server based on the cli parameters.
117
+ def setup_application
118
+ Souffle::Daemon.change_privilege
119
+ Souffle::Config[:server] = true if Souffle::Config[:daemonize]
120
+ @app = Souffle::Server.new
121
+ end
44
122
 
123
+ # Runs the Souffle Server.
124
+ def run_application
125
+ if Souffle::Config[:daemonize]
126
+ Souffle::Config[:server] = true
127
+ Souffle::Daemon.daemonize("souffle")
128
+ end
129
+ @app.run
45
130
  end
46
131
  end
@@ -2,74 +2,103 @@ require 'souffle/log'
2
2
  require 'mixlib/config'
3
3
  require 'yajl'
4
4
 
5
- module Souffle
6
- # The configuration object for the souffle server.
7
- class Config
5
+ # The configuration object for the souffle server.
6
+ class Souffle::Config
7
+ extend Mixlib::Config
8
8
 
9
- extend Mixlib::Config
9
+ # Return the configuration itself upon inspection.
10
+ def self.inspect
11
+ configuration.inspect
12
+ end
10
13
 
11
- # Return the configuration itself upon inspection.
12
- def self.inspect
13
- configuration.inspect
14
- end
14
+ # Loads a given file and passes it to the appropriate parser.
15
+ #
16
+ # @raise [ IOError ] Any IO Exceptions that occur.
17
+ #
18
+ # @param [ String ] filename The filename to read.
19
+ def self.from_file(filename, parser="ruby")
20
+ send("from_file_#{parser}".to_sym, filename)
21
+ end
15
22
 
16
- # Loads a given file and passes it to the appropriate parser.
17
- #
18
- # @raise [ IOError ] Any IO Exceptions that occur.
19
- #
20
- # @param [ String ] filename The filename to read.
21
- def self.from_file(filename, parser="ruby")
22
- send("from_file_#{parser}".to_sym, filename)
23
- end
23
+ # Loads a given ruby file and runs instance_eval against it
24
+ # in the context of the current object.
25
+ #
26
+ # @raise [ IOError ] Any IO Exceptions that occur.
27
+ #
28
+ # @param [ String ] filename The file to read.
29
+ def self.from_file_ruby(filename)
30
+ self.instance_eval(IO.read(filename), filename, 1)
31
+ end
24
32
 
25
- # Loads a given ruby file and runs instance_eval against it
26
- # in the context of the current object.
27
- #
28
- # @raise [ IOError ] Any IO Exceptions that occur.
29
- #
30
- # @param [ String ] filename The file to read.
31
- def self.from_file_ruby(filename)
32
- self.instance_eval(IO.read(filename), filename, 1)
33
- end
33
+ # Loads a given json file and merges the current context
34
+ # configuration with the updated hash.
35
+ #
36
+ # @raise [ IOError ] Any IO Exceptions that occur.
37
+ # @raise [ Yajl::ParseError ] Raises Yajl Parsing error on improper json.
38
+ #
39
+ # @param [ String ] filename The file to read.
40
+ def self.from_file_json(filename)
41
+ self.from_stream_json(IO.read(filename))
42
+ end
34
43
 
35
- # Loads a given json file and merges the current context
36
- # configuration with the updated hash.
37
- #
38
- # @raise [ IOError ] Any IO Exceptions that occur.
39
- # @raise [ Yajl::ParseError ] Raises Yajl Parsing error on improper json.
40
- #
41
- # @param [ String ] filename The file to read.
42
- def self.from_file_json(filename)
43
- self.from_input_json(IO.read(filename))
44
- end
44
+ # Loads a given json input and merges the current context
45
+ # configuration with the updated hash.
46
+ #
47
+ # @raise [ IOError ] Any IO Exceptions that occur.
48
+ # @raise [ Yajl::ParseError ] Raises Yajl Parsing error on improper json.
49
+ #
50
+ # @param [ String ] input The json configuration input.
51
+ def self.from_stream_json(input)
52
+ parser = Yajl::Parser.new(:symbolize_keys => true)
53
+ configuration.merge!(parser.parse(input))
54
+ end
45
55
 
46
- # Loads a given json input and merges the current context
47
- # configuration with the updated hash.
48
- #
49
- # @raise [ IOError ] Any IO Exceptions that occur.
50
- # @raise [ Yajl::ParseError ] Raises Yajl Parsing error on improper json.
51
- #
52
- # @param [ String ] input The json configuration input.
53
- def self.from_stream_json(input)
54
- parser = Yajl::Parser.new
55
- configuration.merge!(parser.parse(input))
56
+ # When you are using ActiveSupport, they monkey-patch 'daemonize' into
57
+ # Kernel. So while this is basically identical to what method_missing
58
+ # would do, we pull it up here and get a real method written so that
59
+ # things get dispatched properly.
60
+ config_attr_writer :daemonize do |v|
61
+ configure do |c|
62
+ c[:daemonize] = v
56
63
  end
64
+ end
57
65
 
58
- # When you are using ActiveSupport, they monkey-patch 'daemonize' into
59
- # Kernel. So while this is basically identical to what method_missing
60
- # would do, we pull it up here and get a real method written so that
61
- # things get dispatched properly.
62
- config_attr_writer :daemonize do |v|
63
- configure do |c|
64
- c[:daemonize] = v
65
- end
66
- end
66
+ # Configuration Settings
67
+ config_file "/etc/souffle/souffle.rb"
67
68
 
68
- log_level :info
69
- log_location STDOUT
69
+ # Enable debug
70
+ debug false
70
71
 
71
- aws_access_key = ""
72
- aws_access_secret = ""
72
+ # Logging Settings
73
+ log_level :info
74
+ log_location STDOUT
73
75
 
74
- end
76
+ # Chef Settings
77
+ chef_cookbook_path []
78
+ chef_provisioner :solo
79
+ chef_domain "souffle"
80
+
81
+ # Provider Settings
82
+ provider "Vagrant"
83
+
84
+ # Daemonization Settings
85
+ user nil
86
+ group nil
87
+ umask 0022
88
+
89
+ pid_file nil
90
+
91
+ # AWS Settings
92
+ aws_access_key ""
93
+ aws_access_secret ""
94
+ aws_region "us-west-1"
95
+ delete_on_termination true
96
+
97
+ # Rack Settings
98
+ rack_host "0.0.0.0"
99
+ rack_port 8080
100
+ rack_environment "development"
101
+
102
+ # Vagrant Settings
103
+ vagrant_dir "#{ENV['HOME']}/vagrant/vms"
75
104
  end
@@ -0,0 +1,156 @@
1
+ require 'etc'
2
+
3
+ # Daemon helper routines.
4
+ class Souffle::Daemon
5
+ class << self
6
+ attr_accessor :name
7
+
8
+ # Daemonize the current process, managing pidfiles and process uid/gid.
9
+ #
10
+ # @param [ String ] name The name to be used for the pid file
11
+ def daemonize(name)
12
+ @name = name
13
+ pid = pid_from_file
14
+ unless running?
15
+ remove_pid_file()
16
+ Souffle::Log.info("Daemonizing...")
17
+ begin
18
+ exit if fork; Process.setsid; exit if fork
19
+ msg = "Forked, in #{Process.pid}. "
20
+ msg << "Privileges: #{Process.euid} #{Process.egid}"
21
+ Souffle::Log.info(msg)
22
+ File.umask Souffle::Config[:umask]
23
+ $stdin.reopen("/dev/null")
24
+ $stdout.reopen("/dev/null", "a")
25
+ $stderr.reopen($stdout)
26
+ save_pid_file;
27
+ at_exit { remove_pid_file }
28
+ rescue NotImplementedError => e
29
+ Souffle::Application.fatal!("There is no fork: #{e.message}")
30
+ end
31
+ else
32
+ Souffle::Application.fatal!("Souffle is already running pid #{pid}")
33
+ end
34
+ end
35
+
36
+ # Checks if Souffle is running based on the pid_file.
37
+ #
38
+ # @return [ Boolean ] Whether or not Souffle is running.
39
+ def running?
40
+ if pid_from_file.nil?
41
+ false
42
+ else
43
+ Process.kill(0, pid_from_file)
44
+ true
45
+ end
46
+ rescue Errno::ESRCH, Errno::ENOENT
47
+ false
48
+ rescue Errno::EACCES => e
49
+ msg = "You don't have access to the PID "
50
+ msg << "file at #{pid_file}: #{e.message}"
51
+ Souffle::Application.fatal!(msg)
52
+ end
53
+
54
+ # Gets the pid file for @name.
55
+ #
56
+ # @return [ String ] Location of the pid file for @name.
57
+ def pid_file
58
+ Souffle::Config[:pid_file] or "/tmp/#{@name}.pid"
59
+ end
60
+
61
+ # Sucks the pid out of pid_file.
62
+ #
63
+ # @return [ Fixnum,NilClass ] The PID from pid_file or nil if it doesn't
64
+ # exist.
65
+ def pid_from_file
66
+ File.read(pid_file).chomp.to_i
67
+ rescue Errno::ENOENT, Errno::EACCES
68
+ nil
69
+ end
70
+
71
+ # Store the PID on the filesystem.
72
+ #
73
+ # @note
74
+ # This uses the Souffle::Config[:pid_file] option or "/tmp/name.pid"
75
+ # by default.
76
+ def save_pid_file
77
+ file = pid_file
78
+ begin
79
+ FileUtils.mkdir_p(File.dirname(file))
80
+ rescue Errno::EACCES => e
81
+ msg = "Failed store pid in #{File.dirname(file)}, "
82
+ msg << "permission denied: #{e.message}"
83
+ Souffle::Application.fatal!(msg)
84
+ end
85
+
86
+ begin
87
+ File.open(file, "w") { |f| f.write(Process.pid.to_s) }
88
+ rescue Errno::EACCES => e
89
+ msg = "Couldn't write to pidfile #{file}, "
90
+ msg << "permission denied: #{e.message}"
91
+ Souffle::Application.fatal!(msg)
92
+ end
93
+ end
94
+
95
+ # Delete the PID from the filesystem
96
+ def remove_pid_file
97
+ FileUtils.rm(pid_file) if File.exists?(pid_file)
98
+ end
99
+
100
+ # Change process user/group to those specified in Souffle::Config
101
+ def change_privilege
102
+ Dir.chdir("/")
103
+
104
+ msg = "About to change privilege to "
105
+ if Souffle::Config[:user] and Souffle::Config[:group]
106
+ msg << "#{Souffle::Config[:user]}:#{Souffle::Config[:group]}"
107
+ Souffle::Log.info(msg)
108
+ _change_privilege(Souffle::Config[:user], Souffle::Config[:group])
109
+ elsif Souffle::Config[:user]
110
+ msg << "#{Souffle::Config[:user]}"
111
+ Souffle::Log.info(msg)
112
+ _change_privilege(Souffle::Config[:user])
113
+ end
114
+ end
115
+
116
+ # Change privileges of the process to be the specified user and group
117
+ #
118
+ # @param [ String ] user The user to change the process to.
119
+ # @param [ String ] group The group to change the process to.
120
+ #
121
+ # @note
122
+ # The group parameter defaults to user unless specified.
123
+ def _change_privilege(user, group=user)
124
+ uid, gid = Process.euid, Process.egid
125
+
126
+ begin
127
+ target_uid = Etc.getpwnam(user).uid
128
+ rescue ArgumentError => e
129
+ msg = "Failed to get UID for user #{user}, does it exist? "
130
+ msg << e.message
131
+ Souffle::Application.fatal!(msg)
132
+ return false
133
+ end
134
+
135
+ begin
136
+ target_gid = Etc.getgrnam(group).gid
137
+ rescue ArgumentError => e
138
+ msg = "Failed to get GID for group #{group}, does it exist? "
139
+ msg << e.message
140
+ Souffle::Application.fatal!(msg)
141
+ return false
142
+ end
143
+
144
+ if (uid != target_uid) or (gid != target_gid)
145
+ Process.initgroups(user, target_gid)
146
+ Process::GID.change_privilege(target_gid)
147
+ Process::UID.change_privilege(target_uid)
148
+ end
149
+ true
150
+ rescue Errno::EPERM => e
151
+ msg = "Permission denied when trying to change #{uid}:#{gid} "
152
+ msg << "to #{target_uid}:#{target_gid}. #{e.message}"
153
+ Souffle::Application.fatal!(msg)
154
+ end
155
+ end
156
+ end