Class: VagrantPlugins::XenServer::MyUtil::Uploader

Inherits:
Object
  • Object
show all
Defined in:
lib/vagrant-xenserver/util/uploader.rb

Overview

This class uploads files using various protocols by subprocessing to cURL. cURL is a much more capable and complete upload tool than a hand-rolled Ruby library, so we defer to its expertise.

Constant Summary collapse

USER_AGENT =

Custom user agent provided to cURL so that requests to URL shorteners are properly tracked.

"VagrantXenserver/1.0"

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(source, destination, options = nil) ⇒ Uploader

Returns a new instance of Uploader.



23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
# File 'lib/vagrant-xenserver/util/uploader.rb', line 23

def initialize(source, destination, options=nil)
  options     ||= {}
  
  @logger      = Log4r::Logger.new("vagrant::xenserver::util::uploader")
  @source      = source.to_s
  @destination = destination.to_s
  
  begin
    url = URI.parse(@destination)
    if url.scheme && url.scheme.start_with?("http") && url.user
      auth = "#{url.user}"
      auth += ":#{url.password}" if url.password
      url.user = nil
      url.password = nil
      options[:auth] ||= auth
      @destination = url.to_s
    end
  rescue URI::InvalidURIError
    # Ignore, since its clearly not HTTP
  end

  # Get the various optional values
  @auth        = options[:auth]
  @ca_cert     = options[:ca_cert]
  @ca_path     = options[:ca_path]
  @continue    = options[:continue]
  @headers     = options[:headers]
  @insecure    = options[:insecure]
  @ui          = options[:ui]
  @client_cert = options[:client_cert]
end

Instance Attribute Details

#destinationObject (readonly)

Returns the value of attribute destination.



21
22
23
# File 'lib/vagrant-xenserver/util/uploader.rb', line 21

def destination
  @destination
end

#sourceObject (readonly)

Returns the value of attribute source.



20
21
22
# File 'lib/vagrant-xenserver/util/uploader.rb', line 20

def source
  @source
end

Instance Method Details

#execute_curl(options, subprocess_options, &data_proc) ⇒ Object



144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
# File 'lib/vagrant-xenserver/util/uploader.rb', line 144

def execute_curl(options, subprocess_options, &data_proc)
  options = options.dup
  options << subprocess_options

  # Create the callback that is called if we are interrupted
  interrupted  = false
  int_callback = Proc.new do
    @logger.info("Uploader interrupted!")
    interrupted = true
  end

  # Execute!
  result = Vagrant::Util::Busy.busy(int_callback) do
    Vagrant::Util::Subprocess.execute("curl", *options, &data_proc)
  end

  # If the upload was interrupted, then raise a specific error
  raise Errors::UploaderInterrupted if interrupted

  # If it didn't exit successfully, we need to parse the data and
  # show an error message.
  if result.exit_code != 0
    @logger.warn("Uploader exit code: #{result.exit_code}")
    parts    = result.stderr.split(/\n*curl:\s+\(\d+\)\s*/, 2)
    parts[1] ||= ""
    raise Errors::UploaderError, message: parts[1].chomp
  end

  result
end

#optionsArray<Array, Hash>

Returns the varoius cURL and subprocess options.

Returns:

  • (Array<Array, Hash>)


178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
# File 'lib/vagrant-xenserver/util/uploader.rb', line 178

def options
  # Build the list of parameters to execute with cURL
  options = [
             "--fail",
             "--location",
             "--max-redirs", "10",
             "--user-agent", USER_AGENT,
            ]

  options += ["--cacert", @ca_cert] if @ca_cert
  options += ["--capath", @ca_path] if @ca_path
  options += ["--continue-at", "-"] if @continue
  options << "--insecure" if @insecure
  options << "--cert" << @client_cert if @client_cert
  options << "-u" << @auth if @auth

  if @headers
    Array(@headers).each do |header|
      options << "-H" << header
    end
  end

  # Specify some options for the subprocess
  subprocess_options = {}

  # If we're in Vagrant, then we use the packaged CA bundle
  if Vagrant.in_installer?
    subprocess_options[:env] ||= {}
    subprocess_options[:env]["CURL_CA_BUNDLE"] =
      File.expand_path("cacert.pem", ENV["VAGRANT_INSTALLER_EMBEDDED_DIR"])
  end

  return [options, subprocess_options]
end

#upload!Object

This executes the actual upload, uploading the source file to the destination with the given options used to initialize this class.

If this method returns without an exception, the upload succeeded. An exception will be raised if the upload failed.



61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
# File 'lib/vagrant-xenserver/util/uploader.rb', line 61

def upload!
  options, subprocess_options = self.options
  options += ["--output", "dummy"]
  options << @destination
  options += ["-T", @source]

  # This variable can contain the proc that'll be sent to
  # the subprocess execute.
  data_proc = nil

  if @ui
    # If we're outputting progress, then setup the subprocess to
    # tell us output so we can parse it out.
    subprocess_options[:notify] = :stderr

    progress_data = ""
    progress_regexp = /(\r(.+?))\r/

    # Setup the proc that'll receive the real-time data from
    # the uploader.
    data_proc = Proc.new do |type, data|
      # Type will always be "stderr" because that is the only
      # type of data we're subscribed for notifications.

      # Accumulate progress_data
      progress_data << data

      while true
        # If we have a full amount of column data (two "\r") then
        # we report new progress reports. Otherwise, just keep
        # accumulating.
        match = progress_regexp.match(progress_data)
        break if !match
        data = match[2]
        progress_data.gsub!(match[1], "")

        # Ignore the first \r and split by whitespace to grab the columns
        columns = data.strip.split(/\s+/)

        # COLUMN DATA:
        #
        # 0 - % total
        # 1 - Total size
        # 2 - % received
        # 3 - Received size
        # 4 - % transferred
        # 5 - Transferred size
        # 6 - Average download speed
        # 7 - Average upload speed
        # 9 - Total time
        # 9 - Time spent
        # 10 - Time left
        # 11 - Current speed

        output = "Progress: #{columns[0]}% (Rate: #{columns[11]}/s, Estimated time remaining: #{columns[10]})"
        @ui.clear_line
        @ui.detail(output, new_line: false)
      end
    end
  end

  @logger.info("Uploader starting upload: ")
  @logger.info("  -- Source: #{@source}")
  @logger.info("  -- Destination: #{@destination}")

  begin
    execute_curl(options, subprocess_options, &data_proc)
  ensure
    # If we're outputting to the UI, clear the output to
    # avoid lingering progress meters.
    if @ui
      @ui.clear_line

      # Windows doesn't clear properly for some reason, so we just
      # output one more newline.
#              @ui.detail("") if Platform.windows?
    end
  end

  # Everything succeeded
  true
end