syncwrap 2.7.1 → 2.8.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (40) hide show
  1. checksums.yaml +4 -4
  2. data/History.rdoc +61 -0
  3. data/Manifest.txt +3 -0
  4. data/lib/syncwrap/base.rb +1 -1
  5. data/lib/syncwrap/change_key_listener.rb +56 -0
  6. data/lib/syncwrap/cli.rb +71 -8
  7. data/lib/syncwrap/components/arch.rb +8 -10
  8. data/lib/syncwrap/components/bundle.rb +3 -6
  9. data/lib/syncwrap/components/bundled_iyyov_daemon.rb +3 -8
  10. data/lib/syncwrap/components/bundler_gem.rb +2 -2
  11. data/lib/syncwrap/components/cruby_vm.rb +9 -5
  12. data/lib/syncwrap/components/debian.rb +13 -11
  13. data/lib/syncwrap/components/iyyov.rb +21 -4
  14. data/lib/syncwrap/components/jruby_vm.rb +5 -4
  15. data/lib/syncwrap/components/postgresql.rb +28 -7
  16. data/lib/syncwrap/components/puma.rb +112 -35
  17. data/lib/syncwrap/components/qpid.rb +2 -2
  18. data/lib/syncwrap/components/rhel.rb +65 -34
  19. data/lib/syncwrap/components/run_user.rb +11 -4
  20. data/lib/syncwrap/components/source_tree.rb +5 -1
  21. data/lib/syncwrap/components/tarpit_gem.rb +2 -2
  22. data/lib/syncwrap/components/users.rb +2 -1
  23. data/lib/syncwrap/context.rb +15 -2
  24. data/lib/syncwrap/distro.rb +5 -7
  25. data/lib/syncwrap/shell.rb +2 -2
  26. data/lib/syncwrap/systemd_service.rb +140 -0
  27. data/lib/syncwrap.rb +4 -3
  28. data/sync/etc/systemd/system/puma.service.erb +3 -0
  29. data/sync/etc/systemd/system/puma.socket.erb +15 -0
  30. data/sync/postgresql/postgresql.conf.erb +25 -5
  31. data/test/test_components.rb +14 -2
  32. data/test/test_context.rb +1 -1
  33. data/test/test_context_rput.rb +1 -1
  34. data/test/test_rsync.rb +1 -1
  35. data/test/test_shell.rb +2 -1
  36. data/test/test_space.rb +22 -1
  37. data/test/test_space_main.rb +6 -2
  38. data/test/test_version_support.rb +1 -1
  39. data/test/test_zone_balancer.rb +2 -2
  40. metadata +7 -4
@@ -15,18 +15,23 @@
15
15
  #++
16
16
 
17
17
  require 'syncwrap/component'
18
+ require 'syncwrap/change_key_listener'
19
+ require 'syncwrap/systemd_service'
18
20
 
19
21
  module SyncWrap
20
22
 
21
- # Provision to install, start/restart a Puma HTTP
22
- # server, optionally triggered by a state change key.
23
+ # Provision to install and start/restart a Puma HTTP server
24
+ # instance, optionally triggered by a state change key. Systemd
25
+ # service and socket units are supported.
23
26
  #
24
- # Host component dependencies: RunUser, <ruby>
27
+ # Host component dependencies: <Distro>?, RunUser, <ruby>, SourceTree?
25
28
  class Puma < Component
29
+ include ChangeKeyListener
30
+ include SystemDService
26
31
 
27
32
  # Puma version to install and run, if set. Otherwise assume puma
28
33
  # is bundled with the application (i.e. Bundle) and use bin stubs
29
- # to run. (Default: nil; Example: 2.9.0)
34
+ # to run. (Default: nil; Example: 3.3.0)
30
35
  attr_accessor :puma_version
31
36
 
32
37
  # Path to the application/configuration directory which
@@ -48,16 +53,11 @@ module SyncWrap
48
53
  state: "#{rack_path}/puma.state",
49
54
  control: "unix://#{rack_path}/control",
50
55
  environment: "production",
51
- port: 5874,
52
56
  daemon: !foreground? }.merge( @puma_flags )
53
57
  end
54
58
 
55
59
  protected
56
60
 
57
- # An optional state key to check, indicating changes requiring
58
- # a Puma restart (Default: nil; Example: :source_tree)
59
- attr_accessor :change_key
60
-
61
61
  # Should Puma be restarted even when there were no source bundle
62
62
  # changes? (Default: false)
63
63
  attr_writer :always_restart
@@ -66,21 +66,40 @@ module SyncWrap
66
66
  @always_restart
67
67
  end
68
68
 
69
- # The name of the systemd unit file to create for this instance of
70
- # puma. If specified, the name should include a '.service' suffix.
71
- # (Default: nil -> no unit)
72
- attr_accessor :systemd_unit
69
+ # Deprecated
70
+ alias :systemd_unit :systemd_service
71
+
72
+ # Deprecated
73
+ alias :systemd_unit= :systemd_service=
74
+
75
+ # An array of ListenStream configuration values for
76
+ # #systemd_socket. If a #puma_flags[:port] is specified, this
77
+ # defaults to a single '0.0.0.0:port' stream. Otherwise this
78
+ # setting is required if #systemd_socket is specified.
79
+ attr_writer :listen_streams
80
+
81
+ def listen_streams
82
+ if @listen_streams
83
+ @listen_streams
84
+ elsif p = puma_flags[:port]
85
+ [ "0.0.0.0:#{p}" ]
86
+ else
87
+ raise( "Neither #listen_streams nor #puma_flags[:port] specified" +
88
+ " with Puma#systemd_socket" )
89
+ end
90
+ end
73
91
 
74
92
  public
75
93
 
76
94
  def initialize( opts = {} )
77
95
  @puma_version = nil
78
96
  @always_restart = false
79
- @change_key = nil
80
97
  @rack_path = nil
81
98
  @puma_flags = {}
82
- @systemd_unit = nil
83
99
  super
100
+ if systemd_socket && !systemd_service
101
+ raise "Puma#systemd_service is required when #systemd_socket is specified"
102
+ end
84
103
  end
85
104
 
86
105
  def install
@@ -88,46 +107,78 @@ module SyncWrap
88
107
  gem_install( 'puma', version: puma_version )
89
108
  end
90
109
 
91
- changes = change_key && state[ change_key ]
92
-
93
- if systemd_unit
94
- uchanges = rput( '/etc/systemd/system/puma.service',
95
- "/etc/systemd/system/#{systemd_unit}",
96
- user: :root )
97
- if !uchanges.empty?
98
- systemctl( 'enable', systemd_unit )
99
- end
100
- if( change_key.nil? || changes || uchanges || always_restart? )
101
- systemctl( 'restart', systemd_unit )
102
- else
103
- systemctl( 'start', systemd_unit )
104
- end
110
+ if systemd_service
111
+ install_units( always_restart? || change_key_changes? )
105
112
  else
106
113
  rudo( "( cd #{rack_path}", close: ')' ) do
107
114
  rudo( "if [ -f puma.state -a -e control ]; then",
108
115
  close: bare_else_start ) do
109
- if ( change_key && !changes ) && !always_restart?
110
- rudo 'true' #no-op
111
- else
116
+ if always_restart? || change_key_changes?
112
117
  bare_restart
118
+ else
119
+ rudo 'true' #no-op
113
120
  end
114
121
  end
115
122
  end
123
+ nil
124
+ end
125
+ end
126
+
127
+ def start
128
+ if systemd_service
129
+ super
130
+ else
131
+ bare_start
132
+ end
133
+ end
134
+
135
+ def restart( *args )
136
+ if systemd_service
137
+ super
138
+ else
139
+ bare_restart
140
+ end
141
+ end
142
+
143
+ def stop
144
+ if systemd_service
145
+ super
146
+ else
147
+ bare_stop
148
+ end
149
+ end
150
+
151
+ def status
152
+ if systemd_service
153
+ super
154
+ else
155
+ bare_status
116
156
  end
117
- nil
118
157
  end
119
158
 
120
159
  protected
121
160
 
122
- # By default, runs in foreground if a systemd_unit is specified.
161
+ # By default, runs in foreground if a systemd_service is specified.
123
162
  def foreground?
124
- !!systemd_unit
163
+ !!systemd_service
125
164
  end
126
165
 
127
166
  def bare_restart
128
167
  rudo( ( pumactl_command + %w[ --state puma.state restart ] ).join( ' ' ) )
129
168
  end
130
169
 
170
+ def bare_stop
171
+ rudo( ( pumactl_command + %w[ --state puma.state stop ] ).join( ' ' ) )
172
+ end
173
+
174
+ def bare_status
175
+ rudo( ( pumactl_command + %w[ --state puma.state status ] ).join( ' ' ) )
176
+ end
177
+
178
+ def bare_start
179
+ rudo( "cd #{rack_path} && #{puma_start_command}" )
180
+ end
181
+
131
182
  def bare_else_start
132
183
  <<-SH
133
184
  else
@@ -171,5 +222,31 @@ module SyncWrap
171
222
  '--' + key.to_s.gsub( /_/, '-' )
172
223
  end
173
224
 
225
+ def rput_unit_files
226
+ c = rput( src_for_systemd_service,
227
+ "/etc/systemd/system/#{systemd_service}", user: :root )
228
+ if systemd_socket
229
+ c += rput( src_for_systemd_socket,
230
+ "/etc/systemd/system/#{systemd_socket}", user: :root )
231
+ end
232
+ c
233
+ end
234
+
235
+ def src_for_systemd_service
236
+ s = "/etc/systemd/system/#{systemd_service}"
237
+ unless find_source( s )
238
+ s = '/etc/systemd/system/puma.service'
239
+ end
240
+ s
241
+ end
242
+
243
+ def src_for_systemd_socket
244
+ s = "/etc/systemd/system/#{systemd_socket}"
245
+ unless find_source( s )
246
+ s = '/etc/systemd/system/puma.socket'
247
+ end
248
+ s
249
+ end
250
+
174
251
  end
175
252
  end
@@ -131,7 +131,7 @@ module SyncWrap
131
131
 
132
132
  def corosync_install!( opts = {} )
133
133
  corosync_build
134
- dist_install( "#{corosync_src}/x86_64/*.rpm", succeed: true )
134
+ dist_install( "#{corosync_src}/x86_64/*.rpm" )
135
135
  end
136
136
 
137
137
  def qpid_tools_install!
@@ -287,7 +287,7 @@ module SyncWrap
287
287
  cd /tmp/rpm-drop
288
288
  #{curls.join("\n")}
289
289
  SH
290
- dist_install( "/tmp/rpm-drop/*.rpm", succeed: true )
290
+ dist_install( "/tmp/rpm-drop/*.rpm" )
291
291
  end
292
292
 
293
293
  protected
@@ -33,67 +33,98 @@ module SyncWrap
33
33
 
34
34
  alias :distro_version :rhel_version
35
35
 
36
+ protected
37
+
38
+ # Set true/false to override the default, distro version based
39
+ # determination of whether systemd is PID 1 on the system.
40
+ attr_writer :systemd
41
+
42
+ public
43
+
36
44
  def initialize( opts = {} )
37
45
  super
38
46
  end
39
47
 
40
48
  def systemd?
41
- @is_systemd ||= version_gte?( rhel_version, [7] )
49
+ if @systemd.nil?
50
+ @systemd = version_gte?( rhel_version, [7] )
51
+ end
52
+ @systemd
42
53
  end
43
54
 
44
- # Install the specified package names. A trailing hash is
45
- # interpreted as options, see below.
55
+ # Install the specified packages. When rpm HTTP URLs or local file
56
+ # paths are given instead of package names, these are installed
57
+ # first and individually via #dist_install_url. Calling that
58
+ # explicitly may be preferable. A trailing hash is interpreted as
59
+ # options, see below.
46
60
  #
47
61
  # ==== Options
48
62
  #
49
- # :check_install:: Short-circuit if all packages already
50
- # installed. Thus no upgrades will be performed.
63
+ # :check_install:: Short-circuit if packages are already
64
+ # installed, and thus don't perform updates
65
+ # unless versions are specified. (Default: true)
51
66
  #
52
- # :succeed:: Deprecated, use check_install instead
67
+ # :yum_flags:: Additional array of flags to pass to `yum install`.
53
68
  #
54
- # Additional options are passed to the sudo calls.
69
+ # Options are also passed to the sudo calls.
55
70
  def dist_install( *pkgs )
56
- opts = pkgs.last.is_a?( Hash ) && pkgs.pop.dup || {}
57
- opts.delete( :minimal )
58
- pkgs.flatten!
59
- chk = opts.delete( :check_install )
60
- chk = opts.delete( :succeed ) if chk.nil?
71
+ opts = pkgs.last.is_a?( Hash ) && pkgs.pop || {}
72
+ chk = opts[ :check_install ]
61
73
  chk = check_install? if chk.nil?
62
- dist_if_not_installed?( pkgs, chk, opts ) do
63
- sudo( "yum install -q -y #{pkgs.join( ' ' )}", opts )
74
+ flags = Array( opts[ :yum_flags ] )
75
+ pkgs.flatten!
76
+ rpms,names = pkgs.partition { |p| p =~ /\.rpm$/ || p =~ /^http(s)?:/i }
77
+ rpms.each do |url|
78
+ dist_install_url( url, nil, opts )
79
+ end
80
+ !names.empty? && dist_if_not_installed?( names, chk != false, opts ) do
81
+ sudo( "yum install -q -y #{(flags + names).join ' '}", opts )
64
82
  end
65
83
  end
66
84
 
67
- # Uninstall the specified package names. A trailing hash is
68
- # interpreted as options, see below.
85
+ # Install packages by HTTP URL or local file path to rpm. Uses
86
+ # name to check_install. If not specified, name is deduced via
87
+ # `File.basename( url, '.rpm' )`. It is not recommended to set
88
+ # option `check_install: false`, because `yum` will fail with
89
+ # "Error: Nothing to do" if given a file/URL and the package is
90
+ # already installed.
69
91
  #
70
92
  # ==== Options
71
93
  #
72
- # :succeed:: Succeed even if none of the packages are
73
- # installed. (Deprecated, Default: true)
94
+ # :check_install:: Short-circuit if package is already
95
+ # installed. (Default: true)
74
96
  #
75
- # Additional options are passed to the sudo calls.
97
+ # :yum_flags:: Additional array of flags to pass to `yum install`.
98
+ #
99
+ # Options are also passed to the sudo calls.
100
+ def dist_install_url( url, name = nil, opts = {} )
101
+ name ||= File.basename( url, '.rpm' )
102
+ chk = opts[ :check_install ]
103
+ flags = Array( opts[ :yum_flags ] )
104
+ dist_if_not_installed?( name, chk != false, opts ) do
105
+ sudo( "yum install -q -y #{(flags + [url]).join ' '}", opts )
106
+ end
107
+ end
108
+
109
+ # Uninstall the specified package names. A trailing hash is
110
+ # interpreted as options. These are passed to the sudo.
76
111
  def dist_uninstall( *pkgs )
77
- opts = pkgs.last.is_a?( Hash ) && pkgs.pop.dup || {}
112
+ opts = pkgs.last.is_a?( Hash ) && pkgs.pop || {}
78
113
  pkgs.flatten!
79
- if opts.delete( :succeed ) != false
80
- sudo( <<-SH, opts )
81
- if yum list -C -q installed #{pkgs.join( ' ' )} >/dev/null 2>&1; then
82
- yum remove -q -y #{pkgs.join( ' ' )}
83
- fi
84
- SH
85
- else
86
- sudo( "yum remove -q -y #{pkgs.join( ' ' )}", opts )
87
- end
114
+ sudo( <<-SH, opts )
115
+ if yum list -C -q installed #{pkgs.join ' '} >/dev/null 2>&1; then
116
+ yum remove -q -y #{pkgs.join ' '}
117
+ fi
118
+ SH
88
119
  end
89
120
 
90
- # If chk is true, then wrap block in a sudo bash conditional
91
- # testing if any specified pkgs are not installed. Otherwise just
121
+ # If chk is true, then wrap block in a sudo bash conditional that tests
122
+ # if any specified pkgs are not installed. Otherwise just
92
123
  # yield to block.
93
124
  def dist_if_not_installed?( pkgs, chk, opts, &block )
94
- if chk && pkgs.all? { |p| p !~ /\.rpm$/ && p !~ /^http(s)?:/ }
95
- qry = "yum list -C -q installed #{pkgs.join ' '}"
96
- cnt = qry + " | tail -n +2 | wc -l"
125
+ if chk
126
+ pkgs = Array( pkgs )
127
+ cnt = "rpm -q #{pkgs.join ' '} | grep -cv 'not installed'"
97
128
  cond = %Q{if [ "$(#{cnt})" != "#{pkgs.count}" ]; then}
98
129
  sudo( cond, opts.merge( close: 'fi' ), &block )
99
130
  else
@@ -36,6 +36,9 @@ module SyncWrap
36
36
  @run_dir || "/var/local/#{run_user}"
37
37
  end
38
38
 
39
+ # File mode as integer for the #run_dir (default: 0755)
40
+ attr_accessor :run_dir_mode
41
+
39
42
  # Home directory for the #run_user
40
43
  # (default: nil -> same as #run_dir)
41
44
  attr_writer :run_user_home
@@ -48,6 +51,7 @@ module SyncWrap
48
51
  @run_user = 'runr'
49
52
  @run_group = nil
50
53
  @run_dir = nil
54
+ @run_dir_mode = 0755
51
55
  @run_user_home = nil
52
56
  super
53
57
  end
@@ -73,7 +77,7 @@ module SyncWrap
73
77
  # Create and set owner/permission of run_dir, such that run_user may
74
78
  # create new directories there.
75
79
  def create_run_dir
76
- mkdir_run_user( run_dir )
80
+ mkdir_run_user( run_dir, mode: run_dir_mode )
77
81
  end
78
82
 
79
83
  def service_dir( sname, instance = nil )
@@ -87,11 +91,14 @@ module SyncWrap
87
91
  mkdir_run_user( sdir )
88
92
  end
89
93
 
90
- # Make dir including parents, chown to run_user and chmod 775.
91
- def mkdir_run_user( dir )
94
+ # Make dir including parents via sudo, chown to run_user, and chmod
95
+ # === Options
96
+ # :mode:: Integer file mode for directory set via chmod (Default: 0775)
97
+ def mkdir_run_user( dir, opts = {} )
98
+ mode = opts[:mode] || 0775
92
99
  sudo "mkdir -p #{dir}"
93
100
  chown_run_user dir
94
- sudo "chmod 775 #{dir}"
101
+ sudo( "chmod %o %s" % [ mode, dir ] )
95
102
  end
96
103
 
97
104
  # Deprecated
@@ -53,6 +53,9 @@ module SyncWrap
53
53
  @remote_dir || source_dir
54
54
  end
55
55
 
56
+ # File mode as integer for the #remote_dir (Default: 0755)
57
+ attr_accessor :remote_dir_mode
58
+
56
59
  protected
57
60
 
58
61
  # Require local source_dir to be clean per Git before #rput of the
@@ -84,6 +87,7 @@ module SyncWrap
84
87
  @local_source_root = path_relative_to_caller( '..', clr )
85
88
  @source_dir = nil
86
89
  @remote_dir = nil
90
+ @remote_dir_mode = 0755
87
91
  @remote_source_root = nil
88
92
  @require_clean = true
89
93
  @rput_options = {}
@@ -99,7 +103,7 @@ module SyncWrap
99
103
  end
100
104
 
101
105
  def install
102
- mkdir_run_user( remote_source_path )
106
+ mkdir_run_user( remote_source_path, mode: remote_dir_mode )
103
107
  changes = sync_source
104
108
  on_change( changes ) unless changes.empty?
105
109
  changes
@@ -24,7 +24,7 @@ module SyncWrap
24
24
  #
25
25
  class TarpitGem < Component
26
26
 
27
- # Tarpit version to install (Default: 1.1.0)
27
+ # Tarpit version to install (Default: 2.1.1)
28
28
  attr_accessor :tarpit_version
29
29
 
30
30
  protected
@@ -39,7 +39,7 @@ module SyncWrap
39
39
  public
40
40
 
41
41
  def initialize( opts = {} )
42
- @tarpit_version = '2.1.0'
42
+ @tarpit_version = '2.1.1'
43
43
  @user_install = false
44
44
  super
45
45
  end
@@ -197,7 +197,8 @@ module SyncWrap
197
197
  flags[ :ssh_user ] = ssh_user
198
198
  if ssh_user_pem
199
199
  flags[ :ssh_user_pem ] = ssh_user_pem
200
- flags[ :ssh_options ] = { 'IdentitiesOnly' => 'yes' }
200
+ flags[ :ssh_options ] = { 'IdentitiesOnly' => 'yes',
201
+ 'PasswordAuthentication' => 'no' }
201
202
  end
202
203
  end
203
204
  flags
@@ -93,7 +93,7 @@ module SyncWrap
93
93
  opts = @default_options.merge( opts )
94
94
  close = opts.delete( :close )
95
95
 
96
- flush if opts != @queued_opts #may still be a no-op
96
+ flush unless sh_opts_equal?( @queued_opts, opts ) #may still be a no-op
97
97
 
98
98
  @queued_cmd << command
99
99
  @queued_opts = opts
@@ -185,6 +185,19 @@ module SyncWrap
185
185
 
186
186
  private
187
187
 
188
+ SH_OPT_KEYS = [ :accept, :coalesce, :dryrun, :error, :pipefail,
189
+ :sh_verbose, :ssh_flags, :ssh_options, :ssh_user,
190
+ :ssh_user_pem, :sudo_flags, :user, :verbose ].freeze
191
+
192
+ # Compare options hashes for equality, but only keys that
193
+ # influence execution of #sh, ignoring others.
194
+ def sh_opts_equal?( o1, o2 )
195
+ SH_OPT_KEYS.each do |k|
196
+ return false if o1[k] != o2[k]
197
+ end
198
+ true
199
+ end
200
+
188
201
  def ssh_host_name
189
202
  host.space.ssh_host_name( host )
190
203
  end
@@ -207,7 +220,7 @@ module SyncWrap
207
220
 
208
221
  def rsync( srcs, target, opts )
209
222
  args = rsync_args( ssh_host_name, srcs, target, opts )
210
- exit_code, outputs = capture_stream( args, host, :rsync, opts )
223
+ _,outputs = capture_stream( args, host, :rsync, opts )
211
224
 
212
225
  # Return array of --itemize-changes on standard out.
213
226
  collect_stream( :out, outputs ).
@@ -41,20 +41,19 @@ module SyncWrap
41
41
  #
42
42
  # ==== Options
43
43
  #
44
- # :check_install:: Short-circuit if all packages already
45
- # installed. Thus no upgrades will be performed.
46
- #
47
- # :succeed:: Deprecated, use check_install instead
44
+ # :check_install:: Short-circuit if packages are already
45
+ # installed. Thus no upgrades will be
46
+ # performed. (Default: true)
48
47
  #
49
48
  # :minimal:: Avoid additional "optional" packages when possible
50
49
  #
51
- # Additional options are passed to the sudo calls.
50
+ # Options are also passed to the sudo calls.
52
51
  def dist_install( *pkgs )
53
52
  raise "Include a distro-specific component, e.g. Debian, RHEL"
54
53
  end
55
54
 
56
55
  # Uninstall the specified package names. A trailing hash is
57
- # interpreted as options, passed to the sudo calls.
56
+ # interpreted as options and passed to the sudo calls.
58
57
  def dist_uninstall( *pkgs )
59
58
  raise "Include a distro-specific component, e.g. Debian, RHEL"
60
59
  end
@@ -67,7 +66,6 @@ module SyncWrap
67
66
  # systemctl( 'enable', 'name.service' )
68
67
  #
69
68
  # See #systemd?, SystemD#systemctl
70
- #
71
69
  def dist_install_init_service( name )
72
70
  raise "Include a distro-specific component, e.g. Debian, RHEL"
73
71
  end
@@ -98,7 +98,7 @@ module SyncWrap
98
98
 
99
99
  if opts[ :coalesce ]
100
100
  args << '-c'
101
- cmd = "exec 1>&2\n"
101
+ cmd = String.new( "exec 1>&2\n" )
102
102
  if opts[ :sh_verbose ]
103
103
  cmd << "set " << ( opts[ :sh_verbose ] == :x ? '-x' : '-v' ) << "\n"
104
104
  end
@@ -110,7 +110,7 @@ module SyncWrap
110
110
  args << ( opts[ :sh_verbose ] == :x ? '-x' : '-v' )
111
111
  end
112
112
  args << '-c'
113
- cmd = ""
113
+ cmd = String.new
114
114
  cmd << "cd /\n" if opts[:user]
115
115
  cmd << command_lines_cleanup( command )
116
116
  args << cmd