right_scraper 3.2.6 → 5.0.1

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 (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