syncwrap 1.5.2 → 2.0.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 (107) hide show
  1. checksums.yaml +7 -0
  2. data/History.rdoc +19 -0
  3. data/Manifest.txt +82 -34
  4. data/README.rdoc +96 -48
  5. data/Rakefile +0 -65
  6. data/bin/syncwrap +27 -0
  7. data/examples/LAYOUT.rdoc +70 -0
  8. data/examples/Rakefile +16 -0
  9. data/examples/ec2.rb +44 -0
  10. data/examples/hello.rb +14 -0
  11. data/examples/hello_binding.rb +27 -0
  12. data/examples/jruby.rb +11 -0
  13. data/examples/private/aws.json +4 -0
  14. data/examples/rput.rb +24 -0
  15. data/examples/sync/home/bob/.ssh/authorized_keys +1 -0
  16. data/examples/sync/tmp/sample.erb +3 -0
  17. data/lib/syncwrap/amazon_ec2.rb +236 -0
  18. data/lib/syncwrap/amazon_ws.rb +308 -0
  19. data/lib/syncwrap/base.rb +4 -2
  20. data/lib/syncwrap/cli.rb +328 -0
  21. data/lib/syncwrap/component.rb +443 -0
  22. data/lib/syncwrap/components/commercial_jdk.rb +76 -0
  23. data/lib/syncwrap/components/cruby_vm.rb +144 -0
  24. data/lib/syncwrap/components/etc_hosts.rb +44 -0
  25. data/lib/syncwrap/{geminabox.rb → components/geminabox.rb} +12 -17
  26. data/lib/syncwrap/components/hashdot.rb +97 -0
  27. data/lib/syncwrap/components/iyyov.rb +144 -0
  28. data/lib/syncwrap/components/iyyov_daemon.rb +125 -0
  29. data/lib/syncwrap/components/jruby_vm.rb +122 -0
  30. data/lib/syncwrap/components/mdraid.rb +204 -0
  31. data/lib/syncwrap/components/network.rb +99 -0
  32. data/lib/syncwrap/components/open_jdk.rb +70 -0
  33. data/lib/syncwrap/components/postgresql.rb +159 -0
  34. data/lib/syncwrap/components/qpid.rb +303 -0
  35. data/lib/syncwrap/components/rhel.rb +71 -0
  36. data/lib/syncwrap/components/run_user.rb +99 -0
  37. data/lib/syncwrap/components/ubuntu.rb +85 -0
  38. data/lib/syncwrap/components/users.rb +200 -0
  39. data/lib/syncwrap/context.rb +260 -0
  40. data/lib/syncwrap/distro.rb +53 -60
  41. data/lib/syncwrap/formatter.rb +149 -0
  42. data/lib/syncwrap/host.rb +134 -0
  43. data/lib/syncwrap/main.rb +62 -0
  44. data/lib/syncwrap/path_util.rb +55 -0
  45. data/lib/syncwrap/rsync.rb +227 -0
  46. data/lib/syncwrap/ruby_support.rb +110 -0
  47. data/lib/syncwrap/shell.rb +207 -0
  48. data/lib/syncwrap.rb +367 -1
  49. data/{etc → sync/etc}/gemrc +1 -3
  50. data/sync/etc/hosts.erb +8 -0
  51. data/{etc/init.d/iyyov → sync/etc/init.d/iyyov.erb} +35 -7
  52. data/sync/etc/sysconfig/pgsql/postgresql.erb +2 -0
  53. data/sync/src/hashdot/Makefile.erb +98 -0
  54. data/sync/src/hashdot/profiles/default.hdp.erb +25 -0
  55. data/sync/src/hashdot/profiles/jruby-common.hdp +28 -0
  56. data/sync/src/hashdot/profiles/jruby-shortlived.hdp +9 -0
  57. data/sync/src/hashdot/profiles/jruby.hdp.erb +13 -0
  58. data/sync/src/hashdot/profiles/shortlived.hdp +6 -0
  59. data/sync/var/iyyov/default/config.rb +1 -0
  60. data/sync/var/iyyov/default/daemon.rb.erb +15 -0
  61. data/sync/var/iyyov/jobs.rb.erb +4 -0
  62. data/test/muddled_sync.rb +13 -0
  63. data/test/setup.rb +39 -0
  64. data/test/sync/d1/bar +1 -0
  65. data/test/sync/d1/foo.erb +1 -0
  66. data/test/sync/d3/d2/bar +1 -0
  67. data/test/sync/d3/d2/foo.erb +1 -0
  68. data/test/test_components.rb +108 -0
  69. data/test/test_context.rb +107 -0
  70. data/test/test_context_rput.rb +289 -0
  71. data/test/test_rsync.rb +138 -0
  72. data/test/test_shell.rb +233 -0
  73. data/test/test_space.rb +218 -0
  74. data/test/test_space_main.rb +40 -0
  75. data/test/zfile +1 -0
  76. metadata +204 -71
  77. data/etc/sysconfig/pgsql/postgresql +0 -2
  78. data/lib/syncwrap/aws.rb +0 -448
  79. data/lib/syncwrap/common.rb +0 -161
  80. data/lib/syncwrap/ec2.rb +0 -59
  81. data/lib/syncwrap/hashdot.rb +0 -70
  82. data/lib/syncwrap/iyyov.rb +0 -139
  83. data/lib/syncwrap/java.rb +0 -61
  84. data/lib/syncwrap/jruby.rb +0 -118
  85. data/lib/syncwrap/postgresql.rb +0 -135
  86. data/lib/syncwrap/qpid.rb +0 -251
  87. data/lib/syncwrap/remote_task.rb +0 -199
  88. data/lib/syncwrap/rhel.rb +0 -67
  89. data/lib/syncwrap/ubuntu.rb +0 -78
  90. data/lib/syncwrap/user_run.rb +0 -102
  91. data/test/test_syncwrap.rb +0 -202
  92. data/var/iyyov/jobs.rb +0 -11
  93. /data/{etc → sync/etc}/corosync/corosync.conf +0 -0
  94. /data/{etc → sync/etc}/corosync/uidgid.d/qpid +0 -0
  95. /data/{etc → sync/etc}/init.d/qpidd +0 -0
  96. /data/{etc → sync/etc}/sysctl.d/61-postgresql-shm.conf +0 -0
  97. /data/{usr/local → sync/jruby}/bin/jgem +0 -0
  98. /data/{postgresql → sync/postgresql}/rhel/pg_hba.conf +0 -0
  99. /data/{postgresql → sync/postgresql}/rhel/pg_ident.conf +0 -0
  100. /data/{postgresql → sync/postgresql}/rhel/postgresql.conf +0 -0
  101. /data/{postgresql → sync/postgresql}/ubuntu/environment +0 -0
  102. /data/{postgresql → sync/postgresql}/ubuntu/pg_ctl.conf +0 -0
  103. /data/{postgresql → sync/postgresql}/ubuntu/pg_hba.conf +0 -0
  104. /data/{postgresql → sync/postgresql}/ubuntu/pg_ident.conf +0 -0
  105. /data/{postgresql → sync/postgresql}/ubuntu/postgresql.conf +0 -0
  106. /data/{postgresql → sync/postgresql}/ubuntu/start.conf +0 -0
  107. /data/{usr → sync/usr}/local/etc/qpidd.conf +0 -0
@@ -0,0 +1,149 @@
1
+ #--
2
+ # Copyright (c) 2011-2014 David Kellum
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License"); you
5
+ # may not use this file except in compliance with the License. You
6
+ # may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
13
+ # implied. See the License for the specific language governing
14
+ # permissions and limitations under the License.
15
+ #++
16
+
17
+ require 'thread'
18
+ require 'term/ansicolor'
19
+
20
+ module SyncWrap
21
+
22
+ class Formatter
23
+ include Term::ANSIColor
24
+
25
+ attr_reader :io
26
+ attr_reader :lock
27
+ attr_accessor :colorize
28
+
29
+ def initialize( io = $stdout )
30
+ @io = io
31
+ @lock = Mutex.new
32
+ @colorize = true
33
+ @newlined = true
34
+ @backtraces = {}
35
+ end
36
+
37
+ def sync( &block )
38
+ @lock.synchronize( &block )
39
+ end
40
+
41
+ def write_component( host, comp, mth, state )
42
+ io << yellow if colorize
43
+ io << '== ' << host.name << ' ' << comp.class << '#' << mth
44
+ io << ': ' << state
45
+ io << clear if colorize
46
+ io << "\n"
47
+ flush
48
+ end
49
+
50
+ def write_header( host, mode, opts, live = false )
51
+ olist = []
52
+ olist << "-#{opts[:sh_verbose]}" if opts[:sh_verbose] && mode != :rsync
53
+ olist << 'coalesce' if opts[:coalesce]
54
+ olist << 'dryrun' if opts[:dryrun]
55
+ olist << "accept:#{opts[:accept].join ','}" if opts[:accept]
56
+ olist << "user:#{opts[:user]}" if opts[:user]
57
+ olist << 'live' if live
58
+
59
+ io << yellow if colorize
60
+ io << '<-- ' << mode << ' ' << host.name
61
+ first = true
62
+ olist.each do |li|
63
+ if first
64
+ io << ' ('
65
+ first = false
66
+ else
67
+ io << ' '
68
+ end
69
+ io << li
70
+ end
71
+ io << ')' unless olist.empty?
72
+ io << clear if colorize
73
+ io << "\n"
74
+ flush
75
+ end
76
+
77
+ def write_result( result )
78
+ io << yellow if colorize
79
+ io << '--> ' << result
80
+ io << clear if colorize
81
+ io << "\n"
82
+ flush
83
+ end
84
+
85
+ def write_command_outputs( outputs, color = true )
86
+ outputs.each do |stream, buff|
87
+ write_command_output( stream, buff, color )
88
+ end
89
+ output_terminate
90
+ flush
91
+ end
92
+
93
+ def output_terminate
94
+ unless @newlined
95
+ io.puts
96
+ @newlined = true
97
+ end
98
+ end
99
+
100
+ def write_command_output( stream, buff, color = true )
101
+ unless buff.empty?
102
+ if stream == :err && colorize && color
103
+ io << red << buff << clear
104
+ else
105
+ io << buff
106
+ end
107
+ @newlined = ( buff[-1] == "\n" )
108
+ end
109
+ end
110
+
111
+ def write_error( host, error, comp = nil, mth = nil )
112
+ bt = error.backtrace
113
+ bt_num = @backtraces[ bt ]
114
+ if bt_num
115
+ bt = nil
116
+ else
117
+ @backtraces[ bt ] = bt_num = @backtraces.length + 1
118
+ end
119
+
120
+ io << yellow if colorize
121
+ io << '== ' << host.name << ' '
122
+ io << comp.class << '#' << mth << ' ' if comp && mth
123
+ io << "error"
124
+ io << ", same stack as" unless bt
125
+ io << " [" << bt_num << "]:\n"
126
+
127
+ io << red if colorize
128
+ io << short_cn( error.class ) << ': ' << error.message << "\n"
129
+ if bt
130
+ bt.each do |line|
131
+ break if line =~ /execute_component'$/
132
+ io.puts line
133
+ end
134
+ end
135
+ io << clear if colorize
136
+ flush
137
+ end
138
+
139
+ def flush
140
+ io.flush
141
+ end
142
+
143
+ def short_cn( cls )
144
+ cls.name.sub(/^SyncWrap::/,'')
145
+ end
146
+
147
+ end
148
+
149
+ end
@@ -0,0 +1,134 @@
1
+ #--
2
+ # Copyright (c) 2011-2014 David Kellum
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License"); you
5
+ # may not use this file except in compliance with the License. You
6
+ # may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
13
+ # implied. See the License for the specific language governing
14
+ # permissions and limitations under the License.
15
+ #++
16
+
17
+ module SyncWrap
18
+
19
+ # Represents various host (server, machine instance) metadata and
20
+ # serves as a container for #roles and #components.
21
+ class Host
22
+
23
+ # The space in which this host was constructed.
24
+ attr_reader :space
25
+
26
+ # Array of role Symbols or (direct) Component instances in the
27
+ # order added.
28
+ attr_reader :contents
29
+
30
+ attr_reader :props
31
+
32
+ def initialize( space, props = {} )
33
+ @space = space
34
+ @props = {}
35
+ merge_props( props )
36
+ @contents = [ :all ]
37
+ end
38
+
39
+ # Return the :name property.
40
+ def name
41
+ self[ :name ]
42
+ end
43
+
44
+ # Return the property by (Symbol) key
45
+ def []( key )
46
+ @props[ key ]
47
+ end
48
+
49
+ # Set property by (Symbol) key to value. Note that the :roles
50
+ # property key is supported here, but is effectively the same as
51
+ # #add( val ). Roles are only added, never removed.
52
+ def []=( key, val )
53
+ key = key.to_sym
54
+ if key == :roles
55
+ add( *val )
56
+ else
57
+ @props[ key.to_sym ] = val
58
+ end
59
+ val
60
+ end
61
+
62
+ # Merge properties. Note that the :roles property key is
63
+ # supported here, but is affectively the same as #add( val ).
64
+ def merge_props( opts )
65
+ opts.each do |key,val|
66
+ self[ key ] = val
67
+ end
68
+ end
69
+
70
+ def to_h
71
+ @props.merge( roles: roles )
72
+ end
73
+
74
+ # Add any number of roles (by Symbol) or (direct) Component
75
+ # instances.
76
+ def add( *args )
77
+ args.each do |arg|
78
+ case( arg )
79
+ when Symbol
80
+ @contents << arg unless @contents.include?( arg )
81
+ when Component
82
+ @contents << arg
83
+ else
84
+ raise "Invalid host #{name} addition: #{arg.inspect}"
85
+ end
86
+ end
87
+ end
88
+
89
+ # Return an Array of previously added role symbols.
90
+ def roles
91
+ @contents.select { |c| c.is_a?( Symbol ) }
92
+ end
93
+
94
+ # Return a flat Array of Component instances by traversing
95
+ # previously added roles and any direct components in order.
96
+ def components
97
+ @contents.inject([]) do |m,c|
98
+ if c.is_a?( Symbol )
99
+ m += space.role( c )
100
+ else
101
+ m << c
102
+ end
103
+ m
104
+ end
105
+ end
106
+
107
+ # Return the last component added to this Host prior to the given
108
+ # component (either directly or via a role), or nil if there is no
109
+ # such component.
110
+ def prior_component( component )
111
+ last = nil
112
+ @contents.each do |c|
113
+ if c.is_a?( Symbol )
114
+ space.role( c ).each do |rc|
115
+ return last if rc.equal?( component ) #identity
116
+ last = rc
117
+ end
118
+ else
119
+ return last if c.equal?( component ) #identity
120
+ last = c
121
+ end
122
+ end
123
+ nil
124
+ end
125
+
126
+ # Return the _last_ component which is a kind of the specified
127
+ # Class or Module clz, or nil if not found.
128
+ def component( clz )
129
+ components.reverse.find { |c| c.kind_of?( clz ) }
130
+ end
131
+
132
+ end
133
+
134
+ end
@@ -0,0 +1,62 @@
1
+ #--
2
+ # Copyright (c) 2011-2014 David Kellum
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License"); you
5
+ # may not use this file except in compliance with the License. You may
6
+ # obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
13
+ # implied. See the License for the specific language governing
14
+ # permissions and limitations under the License.
15
+ #++
16
+
17
+ require 'syncwrap'
18
+
19
+ module SyncWrap
20
+
21
+ # A limited set of (private) methods for use at the top-level in
22
+ # a sync.rb. All of these methods delegate to the _current_
23
+ # Space.
24
+ module Main
25
+
26
+ private
27
+
28
+ # The current Space
29
+ def space # :doc:
30
+ Space.current
31
+ end
32
+
33
+ # Shorthand for space.role
34
+ def role( *args ) # :doc:
35
+ space.role( *args )
36
+ end
37
+
38
+ # Shorthand for space.role
39
+ def host( *args ) # :doc:
40
+ space.host( *args )
41
+ end
42
+
43
+ # Merge options given, or (without opts) return space.default_options
44
+ def options( opts = nil ) # :doc:
45
+ if opts
46
+ space.merge_default_options( opts )
47
+ else
48
+ space.default_options
49
+ end
50
+ end
51
+
52
+ # Shorthand for space.provider.profile
53
+ def profile( *args ) # :doc:
54
+ space.provider.profile( *args )
55
+ end
56
+
57
+ end
58
+
59
+ end
60
+
61
+ # Extend the top level Object with the Main module
62
+ self.extend SyncWrap::Main
@@ -0,0 +1,55 @@
1
+ #--
2
+ # Copyright (c) 2011-2014 David Kellum
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License"); you
5
+ # may not use this file except in compliance with the License. You may
6
+ # obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
13
+ # implied. See the License for the specific language governing
14
+ # permissions and limitations under the License.
15
+ #++
16
+
17
+ require 'pathname'
18
+
19
+ module SyncWrap
20
+
21
+ # Utility methods for handling paths.
22
+ module PathUtil
23
+
24
+ private
25
+
26
+ def caller_path( clr )
27
+ clr.first =~ /^([^:]+):/ && File.dirname( $1 )
28
+ end
29
+
30
+ # Unless rpath is already absolute, expand it relative to the
31
+ # calling file, as computed from passed in clr (use your
32
+ # Kernel#caller)
33
+ def path_relative_to_caller( rpath, clr ) # :doc:
34
+ unless rpath =~ %r{^/}
35
+ from = caller_path( clr )
36
+ rpath = File.expand_path( rpath, from ) if from
37
+ end
38
+ rpath
39
+ end
40
+
41
+ # Return path relative to PWD if the result is shorter, otherwise
42
+ # return input path. Preserves any trailing '/'.
43
+ def relativize( path ) # :doc:
44
+ p = Pathname.new( path )
45
+ unless p.relative?
46
+ p = p.relative_path_from( Pathname.pwd ).to_s
47
+ p += '/' if path[-1] == '/'
48
+ path = p if p.length < path.length
49
+ end
50
+ path
51
+ end
52
+
53
+ end
54
+
55
+ end
@@ -0,0 +1,227 @@
1
+ #--
2
+ # Copyright (c) 2011-2014 David Kellum
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License"); you
5
+ # may not use this file except in compliance with the License. You may
6
+ # obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
13
+ # implied. See the License for the specific language governing
14
+ # permissions and limitations under the License.
15
+ #++
16
+
17
+ require 'tmpdir'
18
+ require 'erb'
19
+ require 'fileutils'
20
+
21
+ require 'syncwrap/path_util'
22
+
23
+ module SyncWrap
24
+
25
+ # Low level support for rsync command construction and template
26
+ # processing.
27
+ module Rsync
28
+ include PathUtil
29
+
30
+ private
31
+
32
+ def rsync_args( host, srcs, target, opts = {} ) # :doc:
33
+
34
+ # -i --itemize-changes, used for counting changed files
35
+ flags = %w[ -i ]
36
+
37
+ # -r --recursive
38
+ flags << '-r' unless opts[:recursive] == false
39
+
40
+ # -l --links (recreate symlinks on the destination)
41
+ flags << '-l' unless opts[:links] == false
42
+
43
+ # -p --perms (set destination to source permissions)
44
+ # -E --executability (perserve execute only, default)
45
+ if opts[:perms] != false
46
+ if opts[:perms] == :p || opts[:perms].is_a?( String )
47
+ flags << '-p'
48
+ if opts[ :perms ].is_a?( String )
49
+ flags << "--chmod=#{opts[ :perms ]}"
50
+ end
51
+ else
52
+ flags << '-E'
53
+ end
54
+ end
55
+
56
+ # -c --checksum (to determine changes; not just size,time)
57
+ flags << '-c' unless opts[:checksum] == false
58
+
59
+ # -b --backup (make backups)
60
+ flags << '-b' unless opts[:backup] == false
61
+
62
+ # Pass ssh options via -e (--rsh) flag
63
+ ssh_flags = []
64
+ ssh_flags += opts[ :ssh_flags ] if opts[ :ssh_flags ]
65
+ if opts[ :ssh_options ]
66
+ ssh_flags += opts[:ssh_options].map { |o| ['-o', o.join('=')] }.flatten
67
+ end
68
+ if opts[ :ssh_user ]
69
+ ssh_flags += [ '-l', opts[ :ssh_user ] ]
70
+ ssh_flags += [ '-i', opts[ :ssh_user_pem ] ] if opts[ :ssh_user_pem ]
71
+ end
72
+ flags += [ '-e', "ssh #{ssh_flags.join ' '}" ] unless ssh_flags.empty?
73
+
74
+ if opts[ :user ]
75
+ # Use sudo to place files at remote.
76
+ user = opts[ :user ].to_s
77
+ flags << ( if user != 'root'
78
+ "--rsync-path=sudo -u #{user} rsync"
79
+ else
80
+ "--rsync-path=sudo rsync"
81
+ end )
82
+ end
83
+
84
+ excludes = Array( opts[ :excludes ] )
85
+ flags += excludes.map do |e|
86
+ if e == :dev
87
+ '--cvs-exclude'
88
+ else
89
+ "--exclude=#{e}"
90
+ end
91
+ end
92
+
93
+ flags << '-n' if opts[ :dryrun ]
94
+
95
+ target = [ host, target ].join(':') unless host == 'localhost'
96
+
97
+ [ 'rsync', flags, srcs, target ].flatten.compact
98
+ end
99
+
100
+ def expand_implied_target( srcs ) # :doc:
101
+ #FIXME: Honor absolute arg paths?
102
+ if srcs.length == 1
103
+ target = "/" + srcs.first
104
+ target = File.dirname( target ) + '/' unless target =~ %r{/$}
105
+ else
106
+ target = srcs.pop
107
+ end
108
+ [ srcs, target ]
109
+ end
110
+
111
+ # Resolves each srcs path via #resolve_source! Raises SourceNotFound
112
+ # if any not found.
113
+ def resolve_sources( srcs, sync_paths ) # :doc:
114
+ srcs.map { |path| resolve_source!( path, sync_paths ) }
115
+ end
116
+
117
+ # Resolve the specified source path via #resolve_source. Raises
118
+ # SourceNotFound if not found.
119
+ def resolve_source!( path, sync_paths ) # :doc:
120
+ resolve_source( path, sync_paths ) or
121
+ raise( SourceNotFound,
122
+ "#{path.inspect} not found in :sync_paths #{sync_paths.inspect}" )
123
+ end
124
+
125
+ # Resolve the specified source path to the first existing
126
+ # file/directory in sync_paths roots and returns a #relativize
127
+ # path. Also tries with an .erb suffix if a src does not have a
128
+ # trailing '/'. Preserves any trailing '/'. Returns nil if not
129
+ # found.
130
+ def resolve_source( path, sync_paths ) # :doc:
131
+ #FIXME: Honor absolute arg paths?
132
+ found = nil
133
+ sync_paths.each do |r|
134
+ candidate = File.join( r, path )
135
+ if File.exist?( candidate )
136
+ found = candidate
137
+ elsif candidate !~ %r{(\.erb|/)$}
138
+ candidate += '.erb'
139
+ if File.exist?( candidate )
140
+ found = candidate
141
+ end
142
+ end
143
+ break if found
144
+ end
145
+
146
+ found && relativize( found )
147
+ end
148
+
149
+ # Given file path within src, return any sub-directory path needed
150
+ # to reach file, or the empty string. This is also src trailing
151
+ # '/' aware.
152
+ def subpath( src, file ) # :doc:
153
+ src = src.sub( %r{/[^/]*$}, '' ) #remove trail slash or last element
154
+ File.dirname( file ).sub( /^#{src}\/?/, '' )
155
+ end
156
+
157
+ # Process templates in tmpdir and yield post-processed sources to
158
+ # block, cleaning up on exit.
159
+ def process_templates( srcs, opts ) # :doc:
160
+ bnd = opts[ :erb_binding ] or raise "required :erb_binding param missing"
161
+ erb_mode = opts[ :erb_mode ] || '<>' #Trim new line on "<% ... %>\n"
162
+ mktmpdir( 'syncwrap' ) do |tmp_dir|
163
+ processed_sources = []
164
+ out_dir = File.join( tmp_dir, 'd' ) #for default perms
165
+ srcs.each do |src|
166
+ erbs = find_source_erbs( src )
167
+ outname = nil
168
+ erbs.each do |erb|
169
+ spath = subpath( src, erb )
170
+ outname = File.join( out_dir, spath, File.basename( erb, '.erb' ) )
171
+ FileUtils.mkdir_p( File.dirname( outname ) )
172
+ perm = File.stat( erb ).mode
173
+ File.open( outname, "w", perm ) do |fout|
174
+ template = ERB.new( IO.read( erb ), nil, erb_mode )
175
+ template.filename = erb
176
+ fout.puts( template.result( bnd ) )
177
+ end
178
+ end
179
+ if erbs.length == 1 && src == erbs.first
180
+ processed_sources << outname
181
+ elsif !erbs.empty?
182
+ processed_sources << ( out_dir + '/' )
183
+ end
184
+ end
185
+ yield processed_sources
186
+ end
187
+ end
188
+
189
+ # Just like Dir.mktmpdir but with an attempt to workaround a JRuby
190
+ # 1.6.x bug. See https://jira.codehaus.org/browse/JRUBY-5678
191
+ def mktmpdir( prefix ) # :doc:
192
+ old_env_tmpdir = nil
193
+ newdir = nil
194
+ if defined?( JRUBY_VERSION ) && JRUBY_VERSION =~ /^1.6/
195
+ old_env_tmpdir = ENV['TMPDIR']
196
+ newdir = "/tmp/syncwrap.#{ENV['USER']}"
197
+ FileUtils.mkdir_p( newdir, mode: 0700 )
198
+ ENV['TMPDIR'] = newdir
199
+ end
200
+ Dir.mktmpdir( 'syncwrap' ) do |tmp_dir|
201
+ yield tmp_dir
202
+ end
203
+ ensure
204
+ FileUtils.rmdir( newdir ) if newdir
205
+ ENV['TMPDIR'] = old_env_tmpdir if old_env_tmpdir
206
+ end
207
+
208
+ def find_source_erbs( sources ) # :doc:
209
+ Array( sources ).inject([]) do |list, src|
210
+ if File.directory?( src )
211
+ list += find_source_erbs( expand_entries( src ) )
212
+ elsif src =~ /\.erb$/
213
+ list << src
214
+ end
215
+ list
216
+ end
217
+ end
218
+
219
+ def expand_entries( src ) # :doc:
220
+ Dir.entries( src ).
221
+ reject { |e| e =~ /^\.+$/ }.
222
+ map { |e| File.join( src, e ) }
223
+ end
224
+
225
+ end
226
+
227
+ end