right_scraper 3.2.6 → 5.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (92) hide show
  1. checksums.yaml +7 -0
  2. data/lib/right_scraper.rb +16 -34
  3. data/lib/right_scraper/builders.rb +32 -0
  4. data/lib/right_scraper/builders/base.rb +19 -20
  5. data/lib/right_scraper/builders/filesystem.rb +8 -6
  6. data/lib/right_scraper/builders/union.rb +4 -1
  7. data/lib/right_scraper/loggers.rb +31 -0
  8. data/lib/right_scraper/loggers/base.rb +113 -0
  9. data/lib/right_scraper/loggers/default.rb +98 -0
  10. data/lib/right_scraper/{scraper.rb → main.rb} +53 -9
  11. data/lib/right_scraper/processes.rb +33 -0
  12. data/lib/right_scraper/processes/shell.rb +227 -0
  13. data/lib/right_scraper/processes/{ssh.rb → ssh_agent.rb} +4 -0
  14. data/lib/right_scraper/processes/svn_client.rb +117 -0
  15. data/lib/right_scraper/processes/warden.rb +358 -0
  16. data/lib/right_scraper/registered_base.rb +154 -0
  17. data/lib/right_scraper/repositories.rb +33 -0
  18. data/lib/right_scraper/repositories/base.rb +271 -232
  19. data/lib/right_scraper/repositories/download.rb +8 -6
  20. data/lib/right_scraper/repositories/git.rb +8 -9
  21. data/lib/right_scraper/repositories/svn.rb +8 -8
  22. data/lib/right_scraper/resources.rb +32 -0
  23. data/lib/right_scraper/resources/base.rb +5 -1
  24. data/lib/right_scraper/resources/cookbook.rb +34 -27
  25. data/lib/right_scraper/resources/workflow.rb +27 -28
  26. data/lib/right_scraper/retrievers.rb +34 -0
  27. data/lib/right_scraper/retrievers/base.rb +80 -84
  28. data/lib/right_scraper/retrievers/checkout_base.rb +178 -0
  29. data/lib/right_scraper/retrievers/download.rb +125 -117
  30. data/lib/right_scraper/retrievers/git.rb +377 -223
  31. data/lib/right_scraper/retrievers/svn.rb +102 -62
  32. data/lib/right_scraper/scanners.rb +37 -0
  33. data/lib/right_scraper/scanners/base.rb +77 -80
  34. data/lib/right_scraper/scanners/cookbook_manifest.rb +31 -30
  35. data/lib/right_scraper/scanners/cookbook_metadata.rb +380 -35
  36. data/lib/right_scraper/scanners/cookbook_s3_upload.rb +56 -53
  37. data/lib/right_scraper/scanners/union.rb +61 -58
  38. data/lib/right_scraper/scanners/workflow_manifest.rb +55 -54
  39. data/lib/right_scraper/scanners/workflow_metadata.rb +41 -39
  40. data/lib/right_scraper/scanners/workflow_s3_upload.rb +59 -55
  41. data/lib/right_scraper/scrapers.rb +32 -0
  42. data/lib/right_scraper/scrapers/base.rb +217 -205
  43. data/lib/right_scraper/scrapers/cookbook.rb +42 -40
  44. data/lib/right_scraper/scrapers/workflow.rb +57 -58
  45. data/lib/right_scraper/version.rb +3 -0
  46. data/right_scraper.gemspec +12 -16
  47. metadata +57 -163
  48. data/Gemfile +0 -15
  49. data/Rakefile +0 -89
  50. data/lib/right_scraper/logger.rb +0 -107
  51. data/lib/right_scraper/loggers/noisy.rb +0 -85
  52. data/lib/right_scraper/repositories/mock.rb +0 -70
  53. data/lib/right_scraper/retrievers/checkout.rb +0 -79
  54. data/lib/right_scraper/scraper_logger.rb +0 -66
  55. data/lib/right_scraper/svn_client.rb +0 -164
  56. data/right_scraper.rconf +0 -13
  57. data/spec/builder_spec.rb +0 -50
  58. data/spec/cookbook_helper.rb +0 -73
  59. data/spec/cookbook_manifest_spec.rb +0 -93
  60. data/spec/cookbook_s3_upload_spec.rb +0 -159
  61. data/spec/download/download_retriever_spec.rb +0 -118
  62. data/spec/download/download_retriever_spec_helper.rb +0 -72
  63. data/spec/download/download_spec.rb +0 -128
  64. data/spec/download/multi_dir_spec.rb +0 -106
  65. data/spec/download/multi_dir_spec_helper.rb +0 -40
  66. data/spec/git/cookbook_spec.rb +0 -165
  67. data/spec/git/demokey +0 -27
  68. data/spec/git/demokey.pub +0 -1
  69. data/spec/git/password_key +0 -30
  70. data/spec/git/password_key.pub +0 -1
  71. data/spec/git/repository_spec.rb +0 -110
  72. data/spec/git/retriever_spec.rb +0 -553
  73. data/spec/git/retriever_spec_helper.rb +0 -112
  74. data/spec/git/scraper_spec.rb +0 -151
  75. data/spec/git/ssh_spec.rb +0 -174
  76. data/spec/git/url_spec.rb +0 -103
  77. data/spec/logger_spec.rb +0 -185
  78. data/spec/repository_spec.rb +0 -111
  79. data/spec/retriever_spec_helper.rb +0 -146
  80. data/spec/scanner_spec.rb +0 -61
  81. data/spec/scraper_helper.rb +0 -88
  82. data/spec/scraper_spec.rb +0 -147
  83. data/spec/spec_helper.rb +0 -185
  84. data/spec/svn/cookbook_spec.rb +0 -96
  85. data/spec/svn/multi_svn_spec.rb +0 -64
  86. data/spec/svn/multi_svn_spec_helper.rb +0 -40
  87. data/spec/svn/repository_spec.rb +0 -72
  88. data/spec/svn/retriever_spec.rb +0 -266
  89. data/spec/svn/scraper_spec.rb +0 -90
  90. data/spec/svn/svn_retriever_spec_helper.rb +0 -90
  91. data/spec/svn/url_spec.rb +0 -47
  92. data/spec/url_spec.rb +0 -164
@@ -1,5 +1,5 @@
1
1
  #--
2
- # Copyright: Copyright (c) 2010-2011 RightScale, Inc.
2
+ # Copyright: Copyright (c) 2010-2013 RightScale, Inc.
3
3
  #
4
4
  # Permission is hereby granted, free of charge, to any person obtaining
5
5
  # a copy of this software and associated documentation files (the
@@ -20,18 +20,26 @@
20
20
  # TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
21
21
  # SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
22
  #++
23
- require File.expand_path(File.join(File.dirname(__FILE__), 'logger'))
23
+
24
+ # ancestor
25
+ require 'right_scraper'
26
+
27
+ require 'fileutils'
24
28
 
25
29
  module RightScraper
26
30
 
27
31
  # Library main entry point. Instantiate this class and call the scrape
28
32
  # method to download or update a remote repository to the local disk and
29
33
  # run a scraper on the resulting files.
30
- class Scraper
34
+ #
35
+ # Note that this class was known as Scraper in v1-3 but the name was confusing
36
+ # due to the Scrapers module performing only a subset of the main Scraper
37
+ # class functionality.
38
+ class Main
31
39
 
32
- # (Array):: Scraped resources
40
+ # (Array):: Scraped resources
33
41
  attr_reader :resources
34
-
42
+
35
43
  # Initialize scrape destination directory
36
44
  #
37
45
  # === Options
@@ -40,11 +48,27 @@ module RightScraper
40
48
  # <tt>:max_bytes</tt>:: Maximum number of bytes to read from remote repo, unlimited if nil
41
49
  # <tt>:max_seconds</tt>:: Maximum number of seconds to spend reading from remote repo, unlimited if nil
42
50
  def initialize(options={})
51
+ options = {
52
+ :kind => nil,
53
+ :basedir => nil,
54
+ :max_bytes => nil,
55
+ :max_seconds => nil,
56
+ :callback => nil,
57
+ :logger => nil,
58
+ :s3_key => nil,
59
+ :s3_secret => nil,
60
+ :s3_bucket => nil,
61
+ :errors => nil,
62
+ :warnings => nil,
63
+ :scanners => nil,
64
+ :builders => nil,
65
+ }.merge(options)
43
66
  @temporary = !options.has_key?(:basedir)
44
67
  options[:basedir] ||= Dir.mktmpdir
45
- @logger = ScraperLogger.new
46
- @options = options.merge({:logger => @logger})
68
+ options[:logger] ||= ::RightScraper::Loggers::Default.new
69
+ @logger = options[:logger]
47
70
  @resources = []
71
+ @options = options
48
72
  end
49
73
 
50
74
  # Scrape given repository, depositing files into the scrape
@@ -80,11 +104,32 @@ module RightScraper
80
104
  begin
81
105
  # 1. Retrieve the files
82
106
  retriever = nil
107
+ repo_dir_changed = false
83
108
  @logger.operation(:retrieving, "from #{repo}") do
109
+ # note that the retriever type may be unavailable but allow the
110
+ # retrieve method to raise any such error.
84
111
  retriever = repo.retriever(@options)
85
- retriever.retrieve if retriever.available?
112
+ repo_dir_changed = retriever.retrieve
86
113
  end
87
114
 
115
+ # TEAL FIX: Note that retrieve will now return true iff there has been
116
+ # a change to the last scraped repository directory for efficiency
117
+ # reasons and only for retreiver types that support this behavior.
118
+ #
119
+ # Even if the retrieval is skipped due to already having the data on
120
+ # disk we still need to scrape its resources only because of the case
121
+ # of the metadata scraper daemon, which updates multiple repositories
122
+ # of similar criteria.
123
+ #
124
+ # The issue is that a new repo can appear later with the same criteria
125
+ # as an already-scraped repo and will need it's own copy of the
126
+ # scraped resources. The easiest (but not most efficient) way to
127
+ # deliver these is to rescrape the already-seen resources. This
128
+ # becomes more expensive as we rely on generating "metadata.json" from
129
+ # "metadata.rb" for cookbooks but is likely not expensive enough to
130
+ # need to improve this logic.
131
+
132
+
88
133
  # 2. Now scrape if there is a scraper in the options
89
134
  @logger.operation(:scraping, retriever.repo_dir) do
90
135
  if @options[:kind]
@@ -139,4 +184,3 @@ module RightScraper
139
184
 
140
185
  end
141
186
  end
142
-
@@ -0,0 +1,33 @@
1
+ #
2
+ # Copyright (c) 2013 RightScale Inc
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person obtaining
5
+ # a copy of this software and associated documentation files (the
6
+ # "Software"), to deal in the Software without restriction, including
7
+ # without limitation the rights to use, copy, modify, merge, publish,
8
+ # distribute, sublicense, and/or sell copies of the Software, and to
9
+ # permit persons to whom the Software is furnished to do so, subject to
10
+ # the following conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be
13
+ # included in all copies or substantial portions of the Software.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
+
23
+ # ancestor
24
+ require 'right_scraper'
25
+
26
+ module RightScraper
27
+ module Processes
28
+ autoload :Shell, 'right_scraper/processes/shell'
29
+ autoload :SSHAgent, 'right_scraper/processes/ssh_agent'
30
+ autoload :SvnClient, 'right_scraper/processes/svn_client'
31
+ autoload :Warden, 'right_scraper/processes/warden'
32
+ end
33
+ end
@@ -0,0 +1,227 @@
1
+ #--
2
+ # Copyright: Copyright (c) 2010-2013 RightScale, Inc.
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person obtaining
5
+ # a copy of this software and associated documentation files (the
6
+ # 'Software'), to deal in the Software without restriction, including
7
+ # without limitation the rights to use, copy, modify, merge, publish,
8
+ # distribute, sublicense, and/or sell copies of the Software, and to
9
+ # permit persons to whom the Software is furnished to do so, subject to
10
+ # the following conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be
13
+ # included in all copies or substantial portions of the Software.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
16
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
18
+ # IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
19
+ # CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
20
+ # TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
21
+ # SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
+ #++
23
+
24
+ # ancestor
25
+ require 'right_scraper/processes'
26
+
27
+ require 'right_git'
28
+ require 'right_popen'
29
+ require 'right_popen/safe_output_buffer'
30
+
31
+ module RightScraper
32
+ module Processes
33
+
34
+ # provides a shell with configurable properties that satisfies the interface
35
+ # for a shell for right_git but can be used for other scraper actions.
36
+ class Shell
37
+ include ::RightGit::Shell::Interface
38
+
39
+ # exceptions.
40
+ class LimitError < ::RightGit::Shell::ShellError; end
41
+
42
+ class SizeLimitError < LimitError; end
43
+ class TimeLimitError < LimitError; end
44
+
45
+ MAX_SAFE_BUFFER_LINE_COUNT = 10
46
+ MAX_SAFE_BUFFER_LINE_LENGTH = 128
47
+
48
+ attr_accessor :initial_directory, :max_seconds, :max_bytes
49
+ attr_accessor :stop_timestamp, :watch_directory
50
+
51
+ # @param [RightScraper::Repositories::Base] retriever associated with shell
52
+ # @param [Hash] options for shell
53
+ # @option options [Integer] :initial_directory for child process (Default = use current directory)
54
+ # @option options [Integer] :max_bytes for interruption (Default = no byte limit)
55
+ # @option options [Integer] :max_seconds for interruption (Default = no time limit)
56
+ # @option options [Integer] :watch_directory for interruption (Default = no byte limit)
57
+ def initialize(options = {})
58
+ options = {
59
+ :initial_directory => nil,
60
+ :max_bytes => nil,
61
+ :max_seconds => nil,
62
+ :watch_directory => nil,
63
+ }.merge(options)
64
+
65
+ @initial_directory = options[:initial_directory]
66
+ @max_bytes = options[:max_bytes]
67
+ @max_seconds = options[:max_seconds]
68
+ @watch_directory = options[:watch_directory]
69
+
70
+ # set stop time once for the lifetime of this shell object.
71
+ @stop_timestamp = (::Time.now + @max_seconds).to_i if @max_seconds
72
+ end
73
+
74
+ # Implements execute interface.
75
+ #
76
+ # @param [String] cmd the shell command to run
77
+ # @param [Hash] options for execution
78
+ #
79
+ # @return [Integer] exitstatus of the command
80
+ #
81
+ # @raise [ShellError] on failure only if :raise_on_failure is true
82
+ def execute(cmd, options = {})
83
+ inner_execute(cmd, :safe_output_handler, options)
84
+ ensure
85
+ @output = nil
86
+ end
87
+
88
+ # Implements output_for interface.
89
+ #
90
+ # @param [String] cmd command to execute
91
+ # @param [Hash] options for execution
92
+ #
93
+ # @return [String] entire output (stdout) of the command
94
+ #
95
+ # @raise [ShellError] on failure only if :raise_on_failure is true
96
+ def output_for(cmd, options = {})
97
+ inner_execute(cmd, :unsafe_output_handler, options)
98
+ @output.display_text
99
+ ensure
100
+ @output = nil
101
+ end
102
+
103
+ # Buffers output safely.
104
+ #
105
+ # @param [String] data
106
+ #
107
+ # @return [TrueClass] always true
108
+ def safe_output_handler(data)
109
+ @output.safe_buffer_data(data)
110
+ true
111
+ end
112
+
113
+ # Buffers output unsafely but completely.
114
+ #
115
+ # @param [String] data
116
+ #
117
+ # @return [TrueClass] always true
118
+ def unsafe_output_handler(data)
119
+ @output.buffer << data.chomp
120
+ true
121
+ end
122
+
123
+ # Raises size limit error.
124
+ #
125
+ # @raise [SizeLimitError] always
126
+ def size_limit_handler
127
+ message =
128
+ "Exceeded size limit of #{@max_bytes / (1024 * 1024)} MB on " +
129
+ "repository directory. Hidden file and directory sizes are not " +
130
+ "included in the total."
131
+ raise SizeLimitError, message
132
+ end
133
+
134
+ # Raises timeout error.
135
+ #
136
+ # @raise [TimeLimitError] always
137
+ def timeout_handler
138
+ raise TimeLimitError, "Timed-out after #{@max_seconds} seconds"
139
+ end
140
+
141
+ # Handles exit status.
142
+ #
143
+ # @param [Status] status after execution
144
+ #
145
+ # @return [TrueClass] always true
146
+ #
147
+ # @raise [ShellError] on execution failure
148
+ def exit_handler(status)
149
+ @exit_code = status.exitstatus
150
+ if @raise_on_failure && !status.success?
151
+ @output.buffer << "Exit code = #{@exit_code}"
152
+ raise ::RightGit::Shell::ShellError, "Execution failed: #{@output.display_text}"
153
+ end
154
+ true
155
+ end
156
+
157
+ private
158
+
159
+ def inner_execute(cmd, output_handler, options)
160
+ options = {
161
+ :directory => nil,
162
+ :logger => nil,
163
+ :outstream => nil,
164
+ :raise_on_failure => true,
165
+ :set_env_vars => nil,
166
+ :clear_env_vars => nil,
167
+ }.merge(options)
168
+ if options[:outstream]
169
+ raise ::ArgumentError, ':outstream is not currently supported'
170
+ end
171
+ @raise_on_failure = options[:raise_on_failure]
172
+
173
+ # max seconds decreases over lifetime of shell until no more commands
174
+ # can be executed due to initial time constraint.
175
+ if @stop_timestamp
176
+ remaining_seconds = @stop_timestamp - ::Time.now.to_i
177
+ min_seconds = 5 # process start, a network gesture, etc.
178
+ timeout_handler if remaining_seconds < min_seconds
179
+ else
180
+ remaining_seconds = nil
181
+ end
182
+
183
+ # set/clear env vars, if requested.
184
+ environment = {}
185
+ if cev = options[:clear_env_vars]
186
+ cev.each { |k| environment[k] = nil }
187
+ end
188
+ if sev = options[:set_env_vars]
189
+ environment.merge!(sev)
190
+ end
191
+ environment = nil if environment.empty?
192
+
193
+ # use safe buffer (allows both safe and unsafe buffering) with limited
194
+ # buffering for output that is only seen on error.
195
+ @output = ::RightScale::RightPopen::SafeOutputBuffer.new(
196
+ buffer = [],
197
+ max_line_count = MAX_SAFE_BUFFER_LINE_COUNT,
198
+ max_line_length = MAX_SAFE_BUFFER_LINE_LENGTH)
199
+
200
+ # directory may be provided, else use initial directory.
201
+ working_directory = options[:directory] || @initial_directory
202
+
203
+ # synchronous popen with watchers, etc.
204
+ if logger = options[:logger]
205
+ logger.info("+ #{cmd}")
206
+ end
207
+ @exit_code = nil
208
+ ::RightScale::RightPopen.popen3_sync(
209
+ cmd,
210
+ :target => self,
211
+ :directory => working_directory,
212
+ :environment => environment,
213
+ :timeout_handler => :timeout_handler,
214
+ :size_limit_handler => :size_limit_handler,
215
+ :exit_handler => :exit_handler,
216
+ :stderr_handler => output_handler,
217
+ :stdout_handler => output_handler,
218
+ :inherit_io => true, # avoid killing any rails connection
219
+ :watch_directory => @watch_directory,
220
+ :size_limit_bytes => @max_bytes,
221
+ :timeout_seconds => remaining_seconds)
222
+ @exit_code
223
+ end
224
+
225
+ end
226
+ end
227
+ end
@@ -21,6 +21,10 @@
21
21
  # SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
22
  #++
23
23
 
24
+ # ancestor
25
+ require 'right_scraper/processes'
26
+
27
+ require 'fileutils'
24
28
  require 'tempfile'
25
29
  require 'tmpdir'
26
30
  require 'right_popen'
@@ -0,0 +1,117 @@
1
+ #--
2
+ # Copyright: Copyright (c) 2010-2013 RightScale, Inc.
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person obtaining
5
+ # a copy of this software and associated documentation files (the
6
+ # 'Software'), to deal in the Software without restriction, including
7
+ # without limitation the rights to use, copy, modify, merge, publish,
8
+ # distribute, sublicense, and/or sell copies of the Software, and to
9
+ # permit persons to whom the Software is furnished to do so, subject to
10
+ # the following conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be
13
+ # included in all copies or substantial portions of the Software.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
16
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
18
+ # IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
19
+ # CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
20
+ # TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
21
+ # SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
+ #++
23
+
24
+ require 'right_popen'
25
+ require 'right_popen/safe_output_buffer'
26
+
27
+ module RightScraper::Processes
28
+
29
+ # Simplified interface to the process of creating SVN client
30
+ # contexts.
31
+ #
32
+ # SVN client contexts are needed for almost every nontrivial SVN
33
+ # operation, and the authentication procedure is truly baroque.
34
+ # Thus, when you need a client context, do something like this:
35
+ # client = SvnClient.new(repository)
36
+ # client.with_context do |ctx|
37
+ # ...
38
+ # end
39
+ class SvnClient
40
+
41
+ class SvnClientError < StandardError; end
42
+
43
+ attr_reader :repository, :shell
44
+
45
+ # @param [RightScraper::Repositories::Base] repository to associate
46
+ # @param [Object] shell for execution
47
+ def initialize(repository, logger, shell)
48
+ unless @repository = repository
49
+ raise ::ArgumentError, 'repository is required'
50
+ end
51
+ unless @logger = logger
52
+ raise ::ArgumentError, 'logger is required'
53
+ end
54
+ unless @shell = shell
55
+ raise ::ArgumentError, 'shell is required'
56
+ end
57
+ end
58
+
59
+ def self.calculate_version
60
+ unless @svn_version
61
+ begin
62
+ cmd = 'svn --version --quiet'
63
+ out = `#{cmd}`
64
+ if $?.success?
65
+ @svn_version = out.chomp.split('.').map {|e| e.to_i}
66
+ else
67
+ raise SvnClientError, "Unable to determine svn version: #{cmd.inspect} exited with #{$?.exitstatus}"
68
+ end
69
+ rescue Errno::ENOENT => e
70
+ raise SvnClientError, "Unable to determine svn version: #{e.message}"
71
+ end
72
+ end
73
+ @svn_version
74
+ end
75
+
76
+ # Executes svn using the given arguments.
77
+ #
78
+ # @param [Array] args for svn
79
+ #
80
+ # @return [TrueClass] always true
81
+ def execute(*args)
82
+ shell.execute(svn_command_for(args), :logger => @logger)
83
+ true
84
+ end
85
+
86
+ # Executes and returns output for svn using the given arguments.
87
+ #
88
+ # @param [Array] args for svn
89
+ #
90
+ # @return [String] output text
91
+ def output_for(*args)
92
+ shell.output_for(svn_command_for(args), :logger => @logger)
93
+ end
94
+
95
+ private
96
+
97
+ def svn_command_for(*args)
98
+ version = self.class.calculate_version
99
+ svn_args = ['svn', args, '--no-auth-cache', '--non-interactive']
100
+ case
101
+ when (version[0] != 1 || version[1] < 4)
102
+ raise SvnClientError, 'SVN client version is unsupported (~> 1.4)'
103
+ when version[1] < 6
104
+ # --trust-server-cert is a 1.6ism
105
+ else
106
+ svn_args << '--trust-server-cert'
107
+ end
108
+ if @repository.first_credential && @repository.second_credential
109
+ svn_args << "--username"
110
+ svn_args << @repository.first_credential
111
+ svn_args << "--password"
112
+ svn_args << @repository.second_credential
113
+ end
114
+ svn_args.flatten.join(' ')
115
+ end
116
+ end
117
+ end