Class: Skylight::NativeExtFetcher

Inherits:
Object
  • Object
show all
Includes:
FileUtils
Defined in:
lib/skylight/native_ext_fetcher.rb

Overview

Utility class for fetching the native extension from a URL

Defined Under Namespace

Classes: FetchError

Constant Summary collapse

BASE_URL =
"https://s3.amazonaws.com/skylight-agent-packages/skylight-native".freeze
MAX_REDIRECTS =
5
MAX_RETRIES =
3

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(source:, target:, version:, checksum:, arch:, logger:, required: false, platform: nil) ⇒ NativeExtFetcher

Returns a new instance of NativeExtFetcher.

Parameters:

  • source (String)

    the base url to download from

  • target (String)

    file to download as

  • version (String)

    version to download

  • checksum (String)

    checksum of the archive

  • arch (String)

    platform architecture, e.g. ‘linux-x86_64`

  • required (Boolean) (defaults to: false)

    whether the download is required to be successful

  • platform (defaults to: nil)
  • log (Logger)


39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
# File 'lib/skylight/native_ext_fetcher.rb', line 39

def initialize(source:, target:, version:, checksum:, arch:, logger:, required: false, platform: nil)
  raise "source required" unless source
  raise "target required" unless target
  raise "checksum required" unless checksum
  raise "arch required" unless arch

  @source = source
  @target = target
  @version = version
  @checksum = checksum
  @required = required
  @platform = platform
  @arch = arch
  @logger = logger
end

Class Method Details

.fetch(**args) ⇒ Object

Creates a new fetcher and fetches

Parameters:

  • opts (Hash)


25
26
27
28
29
# File 'lib/skylight/native_ext_fetcher.rb', line 25

def self.fetch(**args)
  args[:source] ||= BASE_URL
  args[:logger] ||= Logger.new($stdout)
  new(**args).fetch
end

Instance Method Details

#basenameObject



210
211
212
# File 'lib/skylight/native_ext_fetcher.rb', line 210

def basename
  "skylight_#{@arch}.tar.gz"
end

#deconstruct_uri(uri) ⇒ Array<String>

Split the uri string into its component parts

Parameters:

Returns:

  • (Array<String>)

    the host, port, scheme, and request_uri



225
226
227
228
# File 'lib/skylight/native_ext_fetcher.rb', line 225

def deconstruct_uri(uri)
  uri = URI(uri)
  [uri.host, uri.port, uri.scheme == "https", uri.request_uri]
end

#error(msg, err = nil) ⇒ void

This method returns an undefined value.

Log an ‘error` to the `logger`

Parameters:

  • msg (String)
  • e (Exception)

    the exception associated with the error



254
255
256
257
258
# File 'lib/skylight/native_ext_fetcher.rb', line 254

def error(msg, err = nil)
  msg = "[SKYLIGHT] #{msg}"
  msg << "\n#{err.backtrace.join("\n")}" if err
  @logger.error msg
end

#fetchString

Fetch the native extension, verify, inflate, and save (if applicable)

Returns:

  • (String)

    the inflated archive



58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
# File 'lib/skylight/native_ext_fetcher.rb', line 58

def fetch
  log "fetching native ext; curr-platform=#{@platform}; " \
        "requested-arch=#{@arch}; version=#{@version}"

  tar_gz = "#{@target}/#{basename}"

  unless (sha2 = fetch_native_ext(source_uri, tar_gz, MAX_RETRIES, MAX_REDIRECTS))
    maybe_raise "could not fetch native extension"
    return
  end

  unless verify_checksum(sha2)
    maybe_raise "could not verify checksum"
    return
  end

  Dir.chdir File.dirname(tar_gz) do
    cmd = "tar xzvf #{tar_gz}"
    out, _status = Open3.capture2e(cmd)
    log cmd
    log out
  end

  true
ensure
  rm_f tar_gz if tar_gz
end

#fetch_native_ext(uri, out, attempts, redirects) ⇒ Object



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
# File 'lib/skylight/native_ext_fetcher.rb', line 86

def fetch_native_ext(uri, out, attempts, redirects)
  redirects.times do
    # Ensure the location is available
    mkdir_p File.dirname(out)
    rm_f out

    remaining_attempts = attempts

    log "attempting to fetch from remote; uri=#{uri}"

    begin
      host, port, use_ssl, path = deconstruct_uri(uri)

      File.open out, "w" do |f|
        res, extra = http_get(host, port, use_ssl, path, f)

        case res
        when :success
          log "successfully downloaded native ext; out=#{out}"
          return extra
        when :redirect
          log "fetching native ext; uri=#{uri}; redirected=#{res}"
          uri = extra

          next
        end
      end
    rescue StandardError => e
      remaining_attempts -= 1

      error "failed to fetch native extension; uri=#{uri}; msg=#{e.message}; " \
              "remaining-attempts=#{remaining_attempts}",
            e

      if remaining_attempts > 0
        sleep 2
        retry
      end

      return
    end
  end

  log "exceeded max redirects"
  nil
end

#http_get(host, port, use_ssl, path, out) ⇒ Object

Get with ‘Net::HTTP`

If ‘ENV` is set, it will be used as a proxy for this request.

Parameters:

  • host (String)

    host for ‘Net::HTTP` request

  • port (String, Integer)

    port for ‘Net::HTTP` request

  • use_ssl (Boolean)

    whether SSL should be used for this request

  • path (String)

    the path to request

  • out (IO)


142
143
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
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
# File 'lib/skylight/native_ext_fetcher.rb', line 142

def http_get(host, port, use_ssl, path, out)
  if (http_proxy = Util::Proxy.detect_url(ENV))
    log "connecting with proxy: #{http_proxy}"
    uri = URI.parse(http_proxy)
    p_host = uri.host
    p_port = uri.port
    p_user, p_pass = uri.userinfo.split(/:/) if uri.userinfo
  end

  opts = { use_ssl: use_ssl }

  if use_ssl
    # Create a cert store that doesn't enable CRL checking.
    # OpenSSL 3.x may enable CRL checking by default, which fails when
    # the CRL distribution point is unreachable.
    begin
      cert_store = OpenSSL::X509::Store.new
      cert_store.set_default_paths
      ca_file = Util::SSL.ca_cert_file_or_default
      cert_store.add_file(ca_file) if ca_file && File.exist?(ca_file)
      opts[:cert_store] = cert_store
    rescue OpenSSL::X509::StoreError => e
      log "failed to configure cert store: #{e.message}, using defaults"
    end
    opts[:verify_mode] = OpenSSL::SSL::VERIFY_PEER
  end

  Net::HTTP.start(host, port, p_host, p_port, p_user, p_pass, **opts) do |http|
    http.request_get path do |resp|
      case resp
      when Net::HTTPSuccess
        digest = Digest::SHA2.new

        resp.read_body do |chunk|
          digest << chunk
          out.write chunk
        end

        return :success, digest.hexdigest
      when Net::HTTPRedirection
        unless (location = resp["location"])
          raise "received redirect but no location"
        end

        return :redirect, location
      else
        raise "received HTTP status code #{resp.code}"
      end
    end
  end
end

#log(msg) ⇒ void

This method returns an undefined value.

Log an ‘info` to the `logger`

Parameters:



244
245
246
247
# File 'lib/skylight/native_ext_fetcher.rb', line 244

def log(msg)
  msg = "[SKYLIGHT] #{msg}"
  @logger.info msg
end

#maybe_raise(err) ⇒ void

This method returns an undefined value.

Log an error and raise if ‘required` is `true`

Parameters:



234
235
236
237
238
# File 'lib/skylight/native_ext_fetcher.rb', line 234

def maybe_raise(err)
  error err

  raise err if @required
end

#source_uriObject

The url that will be fetched

Returns:

  • String



217
218
219
# File 'lib/skylight/native_ext_fetcher.rb', line 217

def source_uri
  "#{@source}/#{@version}/#{basename}"
end

#verify_checksum(actual) ⇒ Boolean

Verify the checksum of the archive

Parameters:

Returns:

  • (Boolean)

    whether the checksum matches



198
199
200
201
202
203
204
205
206
207
208
# File 'lib/skylight/native_ext_fetcher.rb', line 198

def verify_checksum(actual)
  unless @checksum == actual
    log "checksum mismatch; expected=#{@checksum}; actual=#{actual}"
    return false
  end

  true
rescue Exception => e
  error "failed to read skylight agent archive; e=#{e.message}"
  false
end