nginx_stage 0.3.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.
Files changed (41) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +14 -0
  3. data/.travis.yml +3 -0
  4. data/CHANGELOG.md +199 -0
  5. data/Gemfile +4 -0
  6. data/LICENSE.txt +22 -0
  7. data/README.md +19 -0
  8. data/Rakefile +36 -0
  9. data/bin/node +15 -0
  10. data/bin/ood_ruby +38 -0
  11. data/bin/python +15 -0
  12. data/bin/ruby +15 -0
  13. data/lib/nginx_stage/application.rb +125 -0
  14. data/lib/nginx_stage/configuration.rb +418 -0
  15. data/lib/nginx_stage/errors.rb +46 -0
  16. data/lib/nginx_stage/generator.rb +139 -0
  17. data/lib/nginx_stage/generator_helpers.rb +68 -0
  18. data/lib/nginx_stage/generators/app_clean_generator.rb +38 -0
  19. data/lib/nginx_stage/generators/app_config_generator.rb +101 -0
  20. data/lib/nginx_stage/generators/app_list_generator.rb +24 -0
  21. data/lib/nginx_stage/generators/app_reset_generator.rb +54 -0
  22. data/lib/nginx_stage/generators/nginx_clean_generator.rb +61 -0
  23. data/lib/nginx_stage/generators/nginx_list_generator.rb +22 -0
  24. data/lib/nginx_stage/generators/nginx_process_generator.rb +47 -0
  25. data/lib/nginx_stage/generators/nginx_show_generator.rb +48 -0
  26. data/lib/nginx_stage/generators/pun_config_generator.rb +102 -0
  27. data/lib/nginx_stage/pid_file.rb +37 -0
  28. data/lib/nginx_stage/socket_file.rb +51 -0
  29. data/lib/nginx_stage/user.rb +75 -0
  30. data/lib/nginx_stage/version.rb +4 -0
  31. data/lib/nginx_stage/views/app_config_view.rb +42 -0
  32. data/lib/nginx_stage/views/pun_config_view.rb +144 -0
  33. data/lib/nginx_stage.rb +133 -0
  34. data/nginx_stage.gemspec +24 -0
  35. data/sbin/nginx_stage +7 -0
  36. data/share/nginx_stage_example.yml +166 -0
  37. data/templates/app.conf.erb +14 -0
  38. data/templates/pun.conf.erb +79 -0
  39. data/test/minitest_helper.rb +4 -0
  40. data/test/test_nginx_stage.rb +11 -0
  41. metadata +132 -0
@@ -0,0 +1,38 @@
1
+ module NginxStage
2
+ # This generator cleans up any staged app configs whose app root doesn't
3
+ # exist anymore. It will also print out the paths for the app configs it
4
+ # deleted.
5
+ class AppCleanGenerator < Generator
6
+ desc 'Clean up any staged app configs that point to deleted apps'
7
+
8
+ footer <<-EOF.gsub(/^ {4}/, '')
9
+ Examples:
10
+ To clean up all stale app configs:
11
+
12
+ nginx_stage app_clean
13
+
14
+ this displays the paths of the app configs it deleted.
15
+ EOF
16
+
17
+ # Delete staged app configs whose app doesn't exist anymore. Also display
18
+ # the app configs that are deleted.
19
+ add_hook :delete_stale_app_configs do
20
+ NginxStage.staged_apps.each do |env, apps|
21
+ apps.each do |h|
22
+ owner = h[:owner]
23
+ name = h[:name]
24
+ app_config = NginxStage.app_config_path(env: env, owner: owner, name: name)
25
+ app_root = NginxStage.app_root(env: env, owner: owner, name: name)
26
+ unless NginxStage.as_user(owner) { File.directory?(app_root) }
27
+ begin
28
+ File.delete app_config
29
+ puts app_config
30
+ rescue
31
+ $stderr.puts "#{$!.to_s}"
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,101 @@
1
+ module NginxStage
2
+ # This generator stages and generates the NGINX app config. It is also
3
+ # responsible for reloading the per-user NGINX process after updating the app
4
+ # config.
5
+ class AppConfigGenerator < Generator
6
+ desc 'Generate a new nginx app config and reload process'
7
+
8
+ footer <<-EOF.gsub(/^ {4}/, '')
9
+ Examples:
10
+ To generate an app config from a URI request and reload the nginx
11
+ process:
12
+
13
+ nginx_stage app --user=bob --sub-uri=/pun --sub-request=/usr/jimmy/fillsim/container/13
14
+
15
+ To generate ONLY the app config from a URI request:
16
+
17
+ nginx_stage app --user=bob --sub-uri=/pun --sub-request=/usr/jimmy/fillsim --skip-nginx
18
+
19
+ this will return the app config path and won't run nginx.
20
+ EOF
21
+
22
+ include AppConfigView
23
+
24
+ # The environment the app is run under (parsed from sub_request)
25
+ # @return [Symbol] environment app is run under
26
+ attr_accessor :env
27
+
28
+ # The owner of the app (parsed from sub_request or assume it is user)
29
+ # @return [String] owner of app
30
+ attr_accessor :owner
31
+
32
+ # The name of the app
33
+ # @return [String] name of app
34
+ attr_accessor :name
35
+
36
+ # Accepts `user` as an option and validates user
37
+ add_user_support
38
+
39
+ # Accepts `skip_nginx` as an option
40
+ add_skip_nginx_support
41
+
42
+ # Accepts `sub_uri` as an option
43
+ add_sub_uri_support
44
+
45
+ # @!method sub_request
46
+ # The remainder of the request after the sub-uri used to determine the
47
+ # environment and app
48
+ # @example An app is requested through '/pun/usr/user/appname/...'
49
+ # sub_request #=> "/usr/user/appname/..."
50
+ # @return [String] the remainder of the request after sub-uri
51
+ # @raise [MissingOption] if sub_request isn't supplied
52
+ add_option :sub_request do
53
+ {
54
+ opt_args: ["-r", "--sub-request=SUB_REQUEST", "# The SUB_REQUEST that requests the specified app"],
55
+ required: true
56
+ # sub-request is validated in `NginxStage::parse_app_request`
57
+ }
58
+ end
59
+
60
+ # Parse the sub_request for the environment, owner, and app name
61
+ add_hook :parse_sub_request do
62
+ info = NginxStage.parse_app_request(request: sub_request)
63
+ self.env = info.fetch(:env)
64
+ self.owner = info.fetch(:owner, user)
65
+ self.name = info.fetch(:name, nil)
66
+ end
67
+
68
+ # Validate that the path to the app exists on the local filesystem
69
+ add_hook :validate_app_root do
70
+ raise InvalidRequest, "invalid app root: #{app_root}" unless NginxStage.as_user(user) { File.directory?(app_root) }
71
+ end
72
+
73
+ # Generate the NGINX app config from the 'app.conf.erb' template
74
+ add_hook :create_config do
75
+ template "app.conf.erb", app_config_path
76
+ end
77
+
78
+ # Restart the per-user NGINX process (exit quietly on success)
79
+ add_hook :exec_nginx do
80
+ if !skip_nginx
81
+ if File.file? NginxStage.pun_pid_path(user: user)
82
+ o, s = Open3.capture2e([NginxStage.nginx_bin, "(#{user})"], *NginxStage.nginx_args(user: user, signal: :stop))
83
+ abort(o) unless s.success?
84
+ end
85
+ o, s = Open3.capture2e([NginxStage.nginx_bin, "(#{user})"], *NginxStage.nginx_args(user: user))
86
+ s.success? ? exit : abort(o)
87
+ end
88
+ end
89
+
90
+ # If we skip nginx, then output the path to the generated NGINX app config
91
+ add_hook :output_app_config_path do
92
+ puts app_config_path
93
+ end
94
+
95
+ private
96
+ # NGINX app config path
97
+ def app_config_path
98
+ NginxStage.app_config_path(env: env, owner: owner, name: name)
99
+ end
100
+ end
101
+ end
@@ -0,0 +1,24 @@
1
+ module NginxStage
2
+ # This generator lists all staged apps with app configs.
3
+ class AppListGenerator < Generator
4
+ desc 'List all staged app configs'
5
+
6
+ footer <<-EOF.gsub(/^ {4}/, '')
7
+ Examples:
8
+ To list all staged app configs:
9
+
10
+ nginx_stage app_list
11
+
12
+ this will return the paths to all the staged app configs.
13
+ EOF
14
+
15
+ # Displays a list of all staged app configs
16
+ add_hook :print_staged_app_configs do
17
+ NginxStage.staged_apps.each do |env, apps|
18
+ apps.each do |h|
19
+ puts NginxStage.app_config_path(env: env, owner: h[:owner], name: h[:name])
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,54 @@
1
+ module NginxStage
2
+ # This generator resets all app configs with the most current app config
3
+ # template.
4
+ class AppResetGenerator < Generator
5
+ desc 'Reset all staged app configs with the current template'
6
+
7
+ footer <<-EOF.gsub(/^ {4}/, '')
8
+ Examples:
9
+ To reset all staged app configs using the currently available app
10
+ config template:
11
+
12
+ nginx_stage app_reset --sub-uri=/pun
13
+
14
+ this will return the paths to the newly updated app configs.
15
+ EOF
16
+
17
+ include AppConfigView
18
+
19
+ # The environment the app is run under (parsed from sub_request)
20
+ # @return [Symbol] environment app is run under
21
+ attr_accessor :env
22
+
23
+ # The owner of the app (parsed from sub_request or assume it is user)
24
+ # @return [String] owner of app
25
+ attr_accessor :owner
26
+
27
+ # The name of the app
28
+ # @return [String] name of app
29
+ attr_accessor :name
30
+
31
+ # Accepts `sub_uri` as an option
32
+ add_sub_uri_support
33
+
34
+ # Updates all staged app configs with current template and displays paths
35
+ # to user
36
+ add_hook :update_app_configs do
37
+ NginxStage.staged_apps.each do |env, apps|
38
+ apps.each do |h|
39
+ self.env = env
40
+ self.owner = h[:owner]
41
+ self.name = h[:name]
42
+ template "app.conf.erb", app_config_path
43
+ puts app_config_path
44
+ end
45
+ end
46
+ end
47
+
48
+ private
49
+ # NGINX app config path
50
+ def app_config_path
51
+ NginxStage.app_config_path(env: env, owner: owner, name: name)
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,61 @@
1
+ module NginxStage
2
+ # This generator cleans all running per-user NGINX processes that are
3
+ # inactive (i.e., not active connections).
4
+ class NginxCleanGenerator < Generator
5
+ desc 'Clean all user running PUNs with no active connections'
6
+
7
+ footer <<-EOF.gsub(/^ {4}/, '')
8
+ Examples:
9
+ To clean up any running per-user nginx process with no active
10
+ connections:
11
+
12
+ nginx_stage nginx_clean
13
+
14
+ this displays the users who had their PUNs shutdown.
15
+
16
+ To clean up ALL running per-user nginx processes whether it has an
17
+ active connection or not:
18
+
19
+ nginx_stage nginx_clean --force
20
+
21
+ this also displays the users who had their PUNs shutdown.
22
+
23
+ To ONLY display the users with inactive PUNs:
24
+
25
+ nginx_stage nginx_clean --skip-nginx
26
+
27
+ this won't terminate their per-user nginx process.
28
+ EOF
29
+
30
+ # Whether we forcefully kill all PUNs even if they have connections
31
+ add_option :force do
32
+ {
33
+ opt_args: ["-f", "--[no-]force", "# Force clean ALL per-user nginx processes", "# Default: false"],
34
+ default: false
35
+ }
36
+ end
37
+
38
+ # Accepts `skip_nginx` as an option
39
+ add_skip_nginx_support
40
+
41
+ # Find users with PUNs that have no active sessions and kill the process
42
+ add_hook :delete_puns_of_users_with_no_sessions do
43
+ NginxStage.active_users.each do |u|
44
+ begin
45
+ pid_path = PidFile.new NginxStage.pun_pid_path(user: u)
46
+ raise StalePidFile, "stale pid file: #{pid_path}" unless pid_path.running_process?
47
+ socket = SocketFile.new NginxStage.pun_socket_path(user: u)
48
+ if socket.sessions.zero? || force
49
+ puts u
50
+ if !skip_nginx
51
+ o, s = Open3.capture2e(NginxStage.nginx_bin, *NginxStage.nginx_args(user: u, signal: :stop))
52
+ $stderr.puts o unless s.success?
53
+ end
54
+ end
55
+ rescue
56
+ $stderr.puts "#{$!.to_s}"
57
+ end
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,22 @@
1
+ module NginxStage
2
+ # This generator lists all running per-user NGINX processes.
3
+ class NginxListGenerator < Generator
4
+ desc 'List all user running PUNs'
5
+
6
+ footer <<-EOF.gsub(/^ {4}/, '')
7
+ Examples:
8
+ To list all active per-user nginx processes:
9
+
10
+ nginx_stage nginx_list
11
+
12
+ this lists all users who have actively running PUNs.
13
+ EOF
14
+
15
+ # Display active users
16
+ add_hook :display_active_users do
17
+ NginxStage.active_users.each do |u|
18
+ puts u
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,47 @@
1
+ module NginxStage
2
+ # This generator generates/controls the per-user NGINX process.
3
+ class NginxProcessGenerator < Generator
4
+ desc 'Generate/control a per-user nginx process'
5
+
6
+ footer <<-EOF.gsub(/^ {4}/, '')
7
+ Examples:
8
+ To stop Bob's nginx process:
9
+
10
+ nginx_stage nginx --user=bob --signal=stop
11
+
12
+ which sends a `stop` signal to Bob's per-user NGINX process.
13
+
14
+ If `--skip-nginx` is supplied it returns the system-level command
15
+ that would have been called.
16
+ EOF
17
+
18
+ # Accepts `user` as an option and validates user
19
+ add_user_support
20
+
21
+ # Accepts `skip_nginx` as an option
22
+ add_skip_nginx_support
23
+
24
+ # @!method signal
25
+ # The signal to send to the per-user NGINX process
26
+ # @return [String] nginx signal
27
+ add_option :signal do
28
+ {
29
+ opt_args: ["-s", "--signal=SIGNAL", NginxStage.nginx_signals, "# Send SIGNAL to per-user nginx process: #{NginxStage.nginx_signals.join('/')}"],
30
+ default: nil
31
+ }
32
+ end
33
+
34
+ # Run the per-user NGINX process (exit quietly on success)
35
+ add_hook :exec_nginx do
36
+ if !skip_nginx
37
+ o, s = Open3.capture2e([NginxStage.nginx_bin, "(#{user})"], *NginxStage.nginx_args(user: user, signal: signal))
38
+ s.success? ? exit : abort(o)
39
+ end
40
+ end
41
+
42
+ # If skip nginx, then output nginx command
43
+ add_hook :output_nginx_cmd do
44
+ puts "#{NginxStage.nginx_bin} #{NginxStage.nginx_args(user: user, signal: signal).join(' ')}"
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,48 @@
1
+ module NginxStage
2
+ # This generator shows the state of the running per-user NGINX process.
3
+ class NginxShowGenerator < Generator
4
+ desc 'Show the details for a given per-user nginx process'
5
+
6
+ footer <<-EOF.gsub(/^ {4}/, '')
7
+ Examples:
8
+ To display the details of a running per-user nginx process:
9
+
10
+ nginx_stage nginx_show --user=bob
11
+
12
+ this also displays the number of active sessions connected to this PUN.
13
+ EOF
14
+
15
+ # Accepts 'user' as an option and validates user
16
+ add_user_support
17
+
18
+ # Display the chosen user
19
+ add_hook :display_user do
20
+ puts "User: #{user}"
21
+ end
22
+
23
+ # Check that pid is valid & clean up any stale files
24
+ add_hook :check_pid_is_process do
25
+ pid_file = PidFile.new pid_path
26
+ raise StalePidFile, "stale pid file: #{pid_path}" unless pid_file.running_process?
27
+ puts "Instance: #{pid_file.pid}"
28
+ end
29
+
30
+ # Check for active sessions on Unix domain socket
31
+ add_hook :check_socket_for_active_sessions do
32
+ socket = SocketFile.new socket_path
33
+ puts "Socket: #{socket}"
34
+ puts "Sessions: #{socket.sessions}"
35
+ end
36
+
37
+ private
38
+ # Path to the user's per-user NGINX pid file
39
+ def pid_path
40
+ NginxStage.pun_pid_path(user: user)
41
+ end
42
+
43
+ # Path to the user's per-user NGINX socket file
44
+ def socket_path
45
+ NginxStage.pun_socket_path(user: user)
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,102 @@
1
+ module NginxStage
2
+ # This generator stages and generates the per-user NGINX environment.
3
+ class PunConfigGenerator < Generator
4
+ desc 'Generate a new per-user nginx config and process'
5
+
6
+ footer <<-EOF.gsub(/^ {4}/, '')
7
+ Examples:
8
+ To generate a per-user nginx environment & launch nginx:
9
+
10
+ nginx_stage pun --user=bob --app-init-url='http://www.ood.com/nginx/init?redir=$http_x_forwarded_escaped_uri'
11
+
12
+ this will add a URI redirect if the user accesses an app that doesn't exist.
13
+
14
+ To generate ONLY the per-user nginx environment:
15
+
16
+ nginx_stage pun --user=bob --skip-nginx
17
+
18
+ this will return the per-user nginx config path and won't run nginx. In addition
19
+ it will remove the URI redirect from the config unless we specify `--app-init-url`.
20
+ EOF
21
+
22
+ include PunConfigView
23
+
24
+ # Accepts `user` as an option and validates user
25
+ add_user_support
26
+
27
+ # Block starting up PUNs for users with disabled shells
28
+ add_hook :block_user_with_disabled_shell do
29
+ raise InvalidUser, "user has disabled shell: #{user}" if user.shell == NginxStage.disabled_shell
30
+ end
31
+
32
+ # Accepts `skip_nginx` as an option
33
+ add_skip_nginx_support
34
+
35
+ # @!method app_init_url
36
+ # The app initialization URL the user is redirected to if can't find the
37
+ # app in the per-user NGINX config
38
+ # @return [String] app init redirect url
39
+ add_option :app_init_url do
40
+ {
41
+ opt_args: ["-a", "--app-init-url=APP_INIT_URL", "# The user is redirected to the APP_INIT_URL if app doesn't exist"],
42
+ default: nil,
43
+ before_init: -> (uri) do
44
+ raise InvalidAppInitUri, "invalid app-init-url syntax: #{uri}" if uri =~ /[^-\w\/?$=&.:]/
45
+ uri
46
+ end
47
+ }
48
+ end
49
+
50
+ # Create the user's personal per-user NGINX `/tmp` location for the various
51
+ # nginx cache directories
52
+ add_hook :create_user_tmp_root do
53
+ empty_directory tmp_root
54
+ end
55
+
56
+ # Create the user's personal per-user NGINX `/log` location for the various
57
+ # nginx log files (e.g., 'error.log' & 'access.log')
58
+ add_hook :create_user_log_roots do
59
+ empty_directory File.dirname(error_log_path)
60
+ empty_directory File.dirname(access_log_path)
61
+ end
62
+
63
+ # Create per-user NGINX pid root
64
+ add_hook :create_pid_root do
65
+ empty_directory File.dirname(pid_path)
66
+ end
67
+
68
+ # Create and secure the nginx socket root. The socket file needs to be only
69
+ # accessible by the reverse proxy user.
70
+ add_hook :create_and_secure_socket_root do
71
+ socket_root = File.dirname(socket_path)
72
+ empty_directory socket_root
73
+ FileUtils.chmod 0700, socket_root
74
+ FileUtils.chown NginxStage.proxy_user, nil, socket_root if Process.uid == 0
75
+ end
76
+
77
+ # Generate the per-user NGINX config from the 'pun.conf.erb' template
78
+ add_hook :create_config do
79
+ template "pun.conf.erb", config_path
80
+ end
81
+
82
+ # Run the per-user NGINX process (exit quietly on success)
83
+ add_hook :exec_nginx do
84
+ if !skip_nginx
85
+ o, s = Open3.capture2e([NginxStage.nginx_bin, "(#{user})"], *NginxStage.nginx_args(user: user))
86
+ s.success? ? exit : abort(o)
87
+ end
88
+ end
89
+
90
+ # If skip nginx, then output path to the generated per-user NGINX config
91
+ add_hook :output_pun_config_path do
92
+ puts config_path
93
+ end
94
+
95
+
96
+ private
97
+ # per-user NGINX config path
98
+ def config_path
99
+ NginxStage.pun_config_path(user: user)
100
+ end
101
+ end
102
+ end
@@ -0,0 +1,37 @@
1
+ module NginxStage
2
+ # A class to handle a PID file
3
+ class PidFile
4
+ # Path of the PID file
5
+ # @return [String] the path of the pid file
6
+ attr_reader :pid_path
7
+
8
+ # Process id that describes this object
9
+ # @return [Fixnum] the pid object was initialized with
10
+ attr_reader :pid
11
+
12
+ # @param pid [Fixnum] the pid describing a process
13
+ # @raise [MissingPidFile] if pid file doesn't exist in file system
14
+ # @raise [InvalidPidFile] if it is an invalid pid file
15
+ def initialize(pid_path)
16
+ @pid_path = pid_path
17
+ raise MissingPidFile, "missing PID file: #{pid_path}" unless File.exist?(pid_path)
18
+ raise InvalidPidFile, "invalid PID file: #{pid_path}" unless File.file?(pid_path)
19
+ @pid = File.read(pid_path).to_i
20
+ end
21
+
22
+ # Whether the corresponding pid is a running process
23
+ # @return [Boolean] whether it is a running process
24
+ def running_process?
25
+ Process.getpgid @pid
26
+ true
27
+ rescue Errno::ESRCH
28
+ false
29
+ end
30
+
31
+ # Convert object to string
32
+ # @return [String] the pid path
33
+ def to_s
34
+ pid_path.to_s
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,51 @@
1
+ module NginxStage
2
+ # A class describing a Unix domain socket
3
+ class SocketFile
4
+ # Path to Unix domain socket file
5
+ # @return [String] path to unix domain socket file
6
+ attr_reader :socket
7
+
8
+ # @param socket [String] path to unix domain socket file
9
+ # @raise [MissingSocketFile] if socket file doesn't exist in file system
10
+ # @raise [InvalidSocketFile] if supplied file isn't a valid socket file
11
+ def initialize(socket)
12
+ @socket = socket
13
+ raise MissingSocketFile, "missing socket file: #{socket}" unless File.exist?(socket)
14
+ raise InvalidSocketFile, "invalid socket file: #{socket}" unless File.socket?(socket)
15
+ @processes = get_processes
16
+ end
17
+
18
+ # The number of active sessions connected to this socket
19
+ # @return [Fixnum] number of active connections
20
+ def sessions
21
+ # generate array of inodes
22
+ ary_inodes = @processes.map{|h| h[:inode]}.reduce([], :+)
23
+
24
+ # count number of inodes without partner (assuming these are connected to
25
+ # apache proxy instead of root nginx process)
26
+ ary_inodes.group_by{|e| e}.select{|k,v| v.size == 1}.map(&:first).count
27
+ end
28
+
29
+ # Convert object to string
30
+ # @return [String] path to socket file
31
+ def to_s
32
+ socket
33
+ end
34
+
35
+ private
36
+ def get_processes
37
+ str = `lsof -F piu #{socket}`
38
+ ary = []
39
+ str.split(/\n/).each do |l|
40
+ if /^p(?<pid>\d+)$/ =~ l
41
+ ary << {pid: pid, uid: nil, inode: []}
42
+ elsif /^u(?<uid>\d+)$/ =~ l
43
+ ary.last[:uid] = uid
44
+ elsif /^i(?<inode>\d+)$/ =~ l
45
+ ary.last[:inode] << inode
46
+ end
47
+ end
48
+ ary
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,75 @@
1
+ require 'forwardable'
2
+
3
+ module NginxStage
4
+ # A String-like Class that includes helper methods to better describe the
5
+ # user on the local system.
6
+ class User
7
+ extend Forwardable
8
+
9
+ # @!method name
10
+ # The user name
11
+ # @return [String] the user name
12
+ # @!method uid
13
+ # The user's id
14
+ # @return [Integer] the user id
15
+ # @!method gid
16
+ # The user's group id
17
+ # @return [Integer] the group id
18
+ # @!method gecos
19
+ # The user's real name
20
+ # @return [String] the real name
21
+ # @!method dir
22
+ # The user's home directory
23
+ # @return [String] the home path
24
+ # @!method shell
25
+ # The user's shell
26
+ # @return [String] the shell
27
+ delegate [:name, :uid, :gid, :gecos, :dir, :shell] => :@passwd
28
+
29
+ # List of all groups that user belongs to
30
+ # @return [Array<String>] list of groups user is in
31
+ attr_reader :groups
32
+
33
+ # @param user [String] the user name defining this object
34
+ # @raise [InvalidUser] if user doesn't exist on local system
35
+ def initialize(user)
36
+ @passwd = Etc.getpwnam user.to_s
37
+ @group = Etc.getgrgid gid
38
+ @groups = get_groups
39
+ rescue ArgumentError
40
+ raise InvalidUser, "user doesn't exist: #{user}"
41
+ end
42
+
43
+ # User's primary group name
44
+ # @return [String] the primary group name
45
+ def group
46
+ @group.name
47
+ end
48
+
49
+ # Members of user's primary group
50
+ # @return [Array<String>] list of users in primary group
51
+ def group_mem
52
+ @group.mem
53
+ end
54
+
55
+ # Convert object to string using user name as string value
56
+ # @return [String] the user name
57
+ def to_s
58
+ @passwd.name
59
+ end
60
+
61
+ # This object is string-like and returns the user name when treated as a
62
+ # string
63
+ # @return [String] the user name
64
+ def to_str
65
+ @passwd.name
66
+ end
67
+
68
+ private
69
+ # Use `id` to get list of groups as the /etc/group file can give
70
+ # erroneous results
71
+ def get_groups
72
+ `id -nG #{name}`.split(' ')
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,4 @@
1
+ module NginxStage
2
+ # The current version of NginxStage
3
+ VERSION = "0.3.0"
4
+ end