right_scraper 1.0.23 → 1.0.24

Sign up to get free protection for your applications and to get access to all the features.
@@ -25,11 +25,16 @@ module RightScale
25
25
 
26
26
  # *nix specific watcher implementation
27
27
  class ProcessMonitor
28
-
29
- # Spawn given process and callback given block with output and exit code
28
+ # Spawn given process and callback given block with output and exit code. This method
29
+ # accepts a variable number of parameters; the first param is always the command to
30
+ # run; successive parameters are command-line arguments for the process.
30
31
  #
31
32
  # === Parameters
32
- # cmd(String):: Process command line (including arguments)
33
+ # cmd(String):: Name of the command to run
34
+ # arg1(String):: Optional, first command-line argumument
35
+ # arg2(String):: Optional, first command-line argumument
36
+ # ...
37
+ # argN(String):: Optional, Nth command-line argumument
33
38
  #
34
39
  # === Block
35
40
  # Given block should take one argument which is a hash which may contain
@@ -39,19 +44,31 @@ module RightScale
39
44
  #
40
45
  # === Return
41
46
  # pid(Integer):: Spawned process pid
42
- def spawn(cmd)
43
- # Run external process and monitor it in a new thread
44
- @io = IO.popen(cmd)
47
+ def spawn(cmd, *args)
48
+ args = args.map { |a| a.to_s } #exec only likes string arguments
49
+
50
+ #Run subprocess; capture its output using a pipe
51
+ pr, pw = IO::pipe
52
+ @pid = fork do
53
+ pr.close
54
+ STDIN.reopen(File.open('/dev/null', 'r'))
55
+ STDOUT.reopen(pw)
56
+ STDERR.reopen(pw)
57
+ exec(cmd, *args)
58
+ end
59
+
60
+ #Monitor subprocess output and status in a dedicated thread
61
+ pw.close
62
+ @io = pr
45
63
  @reader = Thread.new do
46
- o = @io.read
47
- until o == ''
48
- yield(:output => o)
49
- o = @io.read
64
+ until @io.eof?
65
+ yield(:output => @io.read)
50
66
  end
51
- Process.wait(@io.pid)
67
+ Process.wait(@pid)
52
68
  yield(:exit_code => $?.exitstatus)
53
69
  end
54
- @io.pid
70
+
71
+ return @pid
55
72
  end
56
73
 
57
74
  # Close io and join reader thread
@@ -140,5 +140,36 @@ module RightScale
140
140
  true
141
141
  end
142
142
 
143
+ # Spawn given process, wait for it to complete, and return its output The exit status
144
+ # of the process is available in the $? global. Functions similarly to the backtick
145
+ # operator, only it avoids invoking the command interpreter under operating systems
146
+ # that support fork-and-exec.
147
+ #
148
+ # This method accepts a variable number of parameters; the first param is always the
149
+ # command to run; successive parameters are command-line arguments for the process.
150
+ #
151
+ # === Parameters
152
+ # cmd(String):: Name of the command to run
153
+ # arg1(String):: Optional, first command-line argumument
154
+ # arg2(String):: Optional, first command-line argumument
155
+ # ...
156
+ # argN(String):: Optional, Nth command-line argumument
157
+ #
158
+ # === Return
159
+ # output(String):: The process' output
160
+ def run(cmd, *args)
161
+ pm = ProcessMonitor.new
162
+ output = StringIO.new
163
+
164
+ pm.spawn(cmd, *args) do |options|
165
+ output << options[:output] if options[:output]
166
+ end
167
+
168
+ pm.cleanup
169
+ output.close
170
+ output = output.string
171
+ return output
172
+ end
173
+
143
174
  end
144
175
  end
@@ -33,10 +33,19 @@ module RightScale
33
33
  msg = "Downloading repository '#{@repo.display_name}'"
34
34
  @callback.call(msg, is_step=true) if @callback
35
35
  filename = @repo.url.split('/').last
36
- user_opt = @repo.first_credential && @repo.second_credential ? "--user #{@repo.first_credential}:#{@repo.second_credential}" : ''
37
- cmd = "curl --fail --silent --show-error --insecure --location #{user_opt} --output \"#{@current_repo_dir}/#{filename}\" '#{@repo.url}' 2>&1"
36
+
37
+ if @repo.first_credential && @repo.second_credential
38
+ user_opt = ['--user', "#{@repo.first_credential}:#{@repo.second_credential}"]
39
+ else
40
+ user_opt = []
41
+ end
42
+
43
+ args = ['--fail', '--silent', '--show-error', '--insecure', '--location']
44
+ args += user_opt
45
+ args += ['--output', "#{@current_repo_dir}/#{filename}", @repo.url]
46
+
38
47
  FileUtils.mkdir_p(@current_repo_dir)
39
- res = @watcher.launch_and_watch(cmd, @current_repo_dir)
48
+ res = @watcher.launch_and_watch('curl', args, @current_repo_dir)
40
49
  handle_watcher_result(res, 'Download')
41
50
  if succeeded?
42
51
  unzip_opt = case @repo.url[/\.(.*)$/]
@@ -45,9 +54,8 @@ module RightScale
45
54
  else ''
46
55
  end
47
56
  Dir.chdir(@current_repo_dir) do
48
- cmd = "tar x#{unzip_opt}f #{filename} 2>&1"
49
- res = `#{cmd}`
50
- @errors << res if $? != 0
57
+ res = run('tar', "x#{unzip_opt}f", filename)
58
+ @errors << res unless $?.success?
51
59
  File.delete(filename)
52
60
  end
53
61
  end
@@ -37,7 +37,7 @@ module RightScale
37
37
  # msysgit or else a native Windows implementation such as Git#
38
38
  return false if (is_windows? || !File.directory?(@current_repo_dir))
39
39
  Dir.chdir(@current_repo_dir) do
40
- remote_url = `git config --get remote.origin.url`.chomp
40
+ remote_url = run('git', 'config', '--get', 'remote.origin.url').chomp
41
41
  $?.success? && remote_url == @repo.url
42
42
  end
43
43
  end
@@ -67,7 +67,7 @@ module RightScale
67
67
  if is_tag && is_branch
68
68
  @errors << 'Repository tag ambiguous: could be git tag or git branch'
69
69
  elsif !is_tag && !is_branch
70
- current_sha = `git rev-parse HEAD`.chomp
70
+ current_sha = run('git', 'rev-parse', 'HEAD').chomp
71
71
  if current_sha == @repo.tag
72
72
  @callback.call("Nothing to update: already using #{@repo.tag}", is_step=false) if @callback
73
73
  return true
@@ -93,8 +93,10 @@ module RightScale
93
93
  end
94
94
 
95
95
  if !@incremental && succeeded?
96
- git_cmd = "#{@ssh_cmd} git clone --quiet --depth 1 \"#{@repo.url}\" \"#{@current_repo_dir}\" 2>&1"
97
- res = @watcher.launch_and_watch(git_cmd, @current_repo_dir)
96
+ args = ['clone', '--quiet', '--depth', '1', @repo.url, @current_repo_dir]
97
+ ENV['GIT_SSH'] = @ssh_cmd
98
+ res = @watcher.launch_and_watch('git', args, @current_repo_dir)
99
+ ENV['GIT_SSH'] = nil
98
100
  handle_watcher_result(res, 'git clone')
99
101
  if has_tag && succeeded?
100
102
  Dir.chdir(@current_repo_dir) do
@@ -109,8 +111,8 @@ module RightScale
109
111
  @errors << 'Repository tag ambiguous: could be git tag or git branch'
110
112
  elsif is_branch
111
113
  if !on_branch
112
- output = `git branch #{@repo.tag} origin/#{@repo.tag} 2>&1`
113
- @errors << output if $? != 0
114
+ output = run('git', 'branch', @repo.tag, "origin/#{repo.tag}")
115
+ @errors << output unless $?.success?
114
116
  end
115
117
  elsif !is_tag # Not a branch nor a tag, SHA ref? fetch everything so we have all SHAs
116
118
  git_fetch(:depth => 2**31 -1)
@@ -167,7 +169,8 @@ module RightScale
167
169
  ssh = File.join(ssh_dir, 'ssh')
168
170
  File.open(ssh, 'w') { |f| f.puts("ssh -F #{ssh_config} $*") }
169
171
  File.chmod(0755, ssh)
170
- "GIT_SSH=#{ssh}"
172
+
173
+ return ssh
171
174
  end
172
175
 
173
176
  # Prepare SSH for git on Windows
@@ -200,7 +203,7 @@ module RightScale
200
203
  # "-o StrictHostKeyChecking=no" in the GIT_SSH executable, but it is
201
204
  # still a mystery why this doesn't work properly in windows.
202
205
  # so make a ssh call which creates the proper "known_hosts" file.
203
- out = `ssh -o StrictHostKeyChecking=no #{repo.url.split(':').first} exit 2>&1`
206
+ run('ssh', '-o', 'StrictHostKeyChecking=no', repo.url.split(':').first)
204
207
  end
205
208
  return ''
206
209
  end
@@ -222,8 +225,10 @@ module RightScale
222
225
  remote = opts[:remote_tag]
223
226
  remote = 'master' if remote.nil? || remote.rstrip.empty?
224
227
  action = (opts[:merge] ? 'pull' : 'fetch')
225
- git_cmd = "#{@ssh_cmd} git #{action} --tags --depth #{depth} origin #{remote} 2>&1"
226
- res = @watcher.launch_and_watch(git_cmd, @current_repo_dir)
228
+ args = [action, '--tags', '--depth', depth, 'origin', remote]
229
+ ENV['GIT_SSH'] = @ssh_cmd
230
+ res = @watcher.launch_and_watch('git', args, @current_repo_dir)
231
+ ENV['GIT_SSH'] = nil
227
232
  handle_watcher_result(res, "git #{action}")
228
233
  end
229
234
 
@@ -237,8 +242,8 @@ module RightScale
237
242
  # === Return
238
243
  # output(String):: Output of git command
239
244
  def git_checkout(tag)
240
- output = `git checkout #{tag} 2>&1`
241
- @errors << output if $? != 0
245
+ output = run('git', 'checkout', tag)
246
+ @errors << output unless $?.success?
242
247
  output
243
248
  end
244
249
 
@@ -255,9 +260,9 @@ module RightScale
255
260
  def analyze_repo_tag
256
261
  is_tag = is_branch = on_branch = nil
257
262
  begin
258
- is_tag = `git tag`.split("\n").include?(@repo.tag)
259
- is_branch = `git branch -r`.split("\n").map { |t| t.strip }.include?("origin/#{@repo.tag}")
260
- on_branch = is_branch && !!`git branch`.split("\n").include?("* #{@repo.tag}")
263
+ is_tag = run('git', 'tag').split("\n").include?(@repo.tag)
264
+ is_branch = run('git', 'branch', '-r').split("\n").map { |t| t.strip }.include?("origin/#{@repo.tag}")
265
+ on_branch = is_branch && !!run('git', 'branch').split("\n").include?("* #{@repo.tag}")
261
266
  rescue Exception => e
262
267
  @errors << "Analysis of repository tag failed with: #{e.message}"
263
268
  end
@@ -38,13 +38,13 @@ module RightScale
38
38
  cookbooks_path = [ cookbooks_path ] unless cookbooks_path.is_a?(Array)
39
39
  if cookbooks_path.empty?
40
40
  Dir.chdir(@current_repo_dir) do
41
- info = `svn info`
41
+ info = run('svn', 'info')
42
42
  inc = $?.success? && info =~ (/^URL: (.*)$/) && $1 == @repo.url
43
43
  end
44
44
  else
45
45
  cookbooks_path.each do |path|
46
46
  Dir.chdir(File.join(@current_repo_dir, path)) do
47
- info = `svn info`
47
+ info = run('svn', 'info')
48
48
  inc = $?.success? && info =~ (/^URL: (.*)$/) && $1 == File.join(@repo.url, path)
49
49
  break unless inc
50
50
  end
@@ -64,14 +64,14 @@ module RightScale
64
64
  cookbooks_path = repo.cookbooks_path || []
65
65
  cookbooks_path = [ cookbooks_path ] unless cookbooks_path.is_a?(Array)
66
66
  if @incremental
67
- svn_cmd = "svn update --no-auth-cache --non-interactive --quiet" +
68
- (!@repo.tag.nil? && !@repo.tag.empty? ? " --revision #{@repo.tag}" : '') +
69
- (@repo.first_credential ? " --username #{@repo.first_credential}" : '') +
70
- (@repo.second_credential ? " --password #{@repo.second_credential}" : '') +
71
- ' 2>&1'
67
+ args = ['update', '--no-auth-cache', '--non-interactive', '--quiet']
68
+ args += ['--revision', @repo.tag] if (!@repo.tag.nil? && !@repo.tag.empty?)
69
+ args += ['--username', @repo.first_credential] if @repo.first_credential
70
+ args += ['--password', @repo.second_credential] if @repo.second_credential
71
+
72
72
  if cookbooks_path.empty?
73
73
  Dir.chdir(@current_repo_dir) do
74
- res = @watcher.launch_and_watch(svn_cmd, @current_repo_dir)
74
+ res = @watcher.launch_and_watch('svn', args, @current_repo_dir)
75
75
  handle_watcher_result(res, 'SVN update')
76
76
  end
77
77
  else
@@ -79,7 +79,7 @@ module RightScale
79
79
  break unless succeeded?
80
80
  full_path = File.join(@current_repo_dir, path)
81
81
  Dir.chdir(full_path) do
82
- res = @watcher.launch_and_watch(svn_cmd, @current_repo_dir)
82
+ res = @watcher.launch_and_watch('svn', args, @current_repo_dir)
83
83
  handle_watcher_result(res, 'SVN update')
84
84
  end
85
85
  end
@@ -87,12 +87,12 @@ module RightScale
87
87
  end
88
88
  if !@incremental && succeeded?
89
89
  if cookbooks_path.empty?
90
- res = @watcher.launch_and_watch(svn_checkout_cmd, @current_repo_dir)
90
+ res = @watcher.launch_and_watch('svn', svn_checkout_args, @current_repo_dir)
91
91
  handle_watcher_result(res, 'SVN checkout')
92
92
  else
93
93
  cookbooks_path.each do |path|
94
94
  break unless succeeded?
95
- res = @watcher.launch_and_watch(svn_checkout_cmd(path), @current_repo_dir)
95
+ res = @watcher.launch_and_watch('svn', svn_checkout_args(path), @current_repo_dir)
96
96
  handle_watcher_result(res, 'SVN checkout')
97
97
  end
98
98
  end
@@ -107,12 +107,13 @@ module RightScale
107
107
  #
108
108
  # === Return
109
109
  # svn_cmd(String):: Corresponding SVN command line
110
- def svn_checkout_cmd(path='')
111
- svn_cmd = "svn checkout \"#{File.join(@repo.url, path)}\" \"#{File.join(@current_repo_dir, path)}\" --no-auth-cache --non-interactive --quiet" +
112
- (!@repo.tag.nil? && !@repo.tag.empty? ? " --revision #{@repo.tag}" : '') +
113
- (@repo.first_credential ? " --username #{@repo.first_credential}" : '') +
114
- (@repo.second_credential ? " --password #{@repo.second_credential}" : '') +
115
- ' 2>&1'
110
+ def svn_checkout_args(path='')
111
+ args = ['checkout', File.join(@repo.url, path), File.join(@current_repo_dir, path),
112
+ '--no-auth-cache', '--non-interactive', '--quiet']
113
+ args += ['--revision', @repo.tag] if !@repo.tag.nil? && !@repo.tag.empty?
114
+ args += ['--username', @repo.first_credential] if @repo.first_credential
115
+ args += ['--password', @repo.second_credential] if @repo.second_credential
116
+ return args
116
117
  end
117
118
  end
118
119
  end
@@ -75,13 +75,13 @@ module RightScale
75
75
  #
76
76
  # === Return
77
77
  # res(RightScale::WatchStatus):: Outcome of watch, see RightScale::WatchStatus
78
- def launch_and_watch(cmd, dest_dir)
78
+ def launch_and_watch(cmd, args, dest_dir)
79
79
  exit_code = nil
80
80
  output = ''
81
81
  monitor = ProcessMonitor.new
82
82
 
83
83
  # Run external process and monitor it in a new thread, platform specific
84
- pid = monitor.spawn(cmd) do |data|
84
+ pid = monitor.spawn(cmd, *args) do |data|
85
85
  output << data[:output] if data[:output]
86
86
  exit_code = data[:exit_code] if data.include?(:exit_code)
87
87
  end
@@ -35,7 +35,11 @@ module RightScale
35
35
  # Spawn given process and callback given block with output and exit code
36
36
  #
37
37
  # === Parameters
38
- # cmd(String):: Process command line (including arguments)
38
+ # cmd(String):: Name of the command to run
39
+ # arg1(String):: Optional, first command-line argumument
40
+ # arg2(String):: Optional, first command-line argumument
41
+ # ...
42
+ # argN(String):: Optional, Nth command-line argumument
39
43
  #
40
44
  # === Block
41
45
  # Given block should take one argument which is a hash which may contain
@@ -45,7 +49,10 @@ module RightScale
45
49
  #
46
50
  # === Return
47
51
  # pid(Integer):: Spawned process pid
48
- def spawn(cmd)
52
+ def spawn(cmd, *args)
53
+ args = args.map { |a| a.to_s }
54
+ cmd = ([cmd] + args).join(' ')
55
+
49
56
  # Run external process and monitor it in a new thread
50
57
  @io = IO.popen(cmd)
51
58
  @handle = OpenProcess(PROCESS_ALL_ACCESS, 0, @io.pid)
@@ -23,7 +23,7 @@ require 'rubygems'
23
23
 
24
24
  spec = Gem::Specification.new do |spec|
25
25
  spec.name = 'right_scraper'
26
- spec.version = '1.0.23'
26
+ spec.version = '1.0.24'
27
27
  spec.authors = ['Raphael Simon']
28
28
  spec.email = 'raphael@rightscale.com'
29
29
  spec.homepage = 'https://github.com/rightscale/right_scraper'
data/spec/watcher_spec.rb CHANGED
@@ -37,7 +37,8 @@ describe RightScale::Watcher do
37
37
 
38
38
  it 'should launch and watch well-behaved processes' do
39
39
  watcher = RightScale::Watcher.new(max_bytes=1, max_seconds=5)
40
- status = watcher.launch_and_watch('ruby -e "puts 42; exit 42"', @dest_dir)
40
+ ruby = "trap('INT', 'IGNORE'); puts 42; exit 42"
41
+ status = watcher.launch_and_watch('ruby', ['-e', ruby], @dest_dir)
41
42
  status.status.should == :success
42
43
  status.exit_code.should == 42
43
44
  status.output.should == "42\n"
@@ -45,7 +46,8 @@ describe RightScale::Watcher do
45
46
 
46
47
  it 'should report timeouts' do
47
48
  watcher = RightScale::Watcher.new(max_bytes=1, max_seconds=2)
48
- status = watcher.launch_and_watch('ruby -e "STDOUT.sync = true; puts 42; sleep 5" 2>&1', @dest_dir)
49
+ ruby = "trap('INT', 'IGNORE'); STDOUT.sync = true; puts 42; sleep 5"
50
+ status = watcher.launch_and_watch('ruby', ['-e', ruby], @dest_dir)
49
51
  status.status.should == :timeout
50
52
  status.exit_code.should == -1
51
53
  status.output.should == "42\n"
@@ -53,7 +55,8 @@ describe RightScale::Watcher do
53
55
 
54
56
  it 'should report size exceeded' do
55
57
  watcher = RightScale::Watcher.new(max_bytes=1, max_seconds=5)
56
- status = watcher.launch_and_watch("ruby -e 'STDOUT.sync = true; puts 42; File.open(File.join(\"#{@dest_dir}\", \"test\"), \"w\") { |f| f.puts \"MORE THAN 2 CHARS\" }'; sleep 5", @dest_dir)
58
+ ruby = "trap('INT', 'IGNORE'); STDOUT.sync = true; puts 42; File.open(File.join('#{@dest_dir}', 'test'), 'w') { |f| f.puts 'MORE THAN 2 CHARS' }; sleep 5 rescue nil"
59
+ status = watcher.launch_and_watch('ruby', ['-e', ruby], @dest_dir)
57
60
  status.status.should == :size_exceeded
58
61
  status.exit_code.should == -1
59
62
  status.output.should == "42\n"
@@ -61,7 +64,8 @@ describe RightScale::Watcher do
61
64
 
62
65
  it 'should allow infinite size and timeout' do
63
66
  watcher = RightScale::Watcher.new(max_bytes=-1, max_seconds=-1)
64
- status = watcher.launch_and_watch("ruby -e 'STDOUT.sync = true; puts 42; File.open(File.join(\"#{@dest_dir}\", \"test\"), \"w\") { |f| f.puts \"MORE THAN 2 CHARS\" };sleep 2'", @dest_dir)
67
+ ruby = "trap('INT', 'IGNORE'); STDOUT.sync = true; puts 42; File.open(File.join('#{@dest_dir}', 'test'), 'w') { |f| f.puts 'MORE THAN 2 CHARS' }; sleep 2 rescue nil"
68
+ status = watcher.launch_and_watch('ruby', ['-e', ruby], @dest_dir)
65
69
  status.status.should == :success
66
70
  status.exit_code.should == 0
67
71
  status.output.should == "42\n"
metadata CHANGED
@@ -1,7 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: right_scraper
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.23
4
+ hash: 39
5
+ prerelease: false
6
+ segments:
7
+ - 1
8
+ - 0
9
+ - 24
10
+ version: 1.0.24
5
11
  platform: ruby
6
12
  authors:
7
13
  - Raphael Simon
@@ -9,7 +15,7 @@ autorequire:
9
15
  bindir: bin
10
16
  cert_chain: []
11
17
 
12
- date: 2010-08-02 00:00:00 -07:00
18
+ date: 2010-08-31 00:00:00 -07:00
13
19
  default_executable:
14
20
  dependencies: []
15
21
 
@@ -62,21 +68,29 @@ rdoc_options:
62
68
  require_paths:
63
69
  - lib
64
70
  required_ruby_version: !ruby/object:Gem::Requirement
71
+ none: false
65
72
  requirements:
66
73
  - - ">="
67
74
  - !ruby/object:Gem::Version
75
+ hash: 59
76
+ segments:
77
+ - 1
78
+ - 8
79
+ - 6
68
80
  version: 1.8.6
69
- version:
70
81
  required_rubygems_version: !ruby/object:Gem::Requirement
82
+ none: false
71
83
  requirements:
72
84
  - - ">="
73
85
  - !ruby/object:Gem::Version
86
+ hash: 3
87
+ segments:
88
+ - 0
74
89
  version: "0"
75
- version:
76
90
  requirements: []
77
91
 
78
92
  rubyforge_project: right_scraper
79
- rubygems_version: 1.3.5
93
+ rubygems_version: 1.3.7
80
94
  signing_key:
81
95
  specification_version: 3
82
96
  summary: Download and update remote repositories