Class: BoltServer::FileCache

Inherits:
Object
  • Object
show all
Defined in:
lib/bolt_server/file_cache.rb

Defined Under Namespace

Classes: Error

Constant Summary collapse

PURGE_TIMEOUT =
60 * 60
PURGE_INTERVAL =
24 * PURGE_TIMEOUT
PURGE_TTL =
7 * PURGE_INTERVAL

Instance Method Summary collapse

Constructor Details

#initialize(config, executor: Concurrent::SingleThreadExecutor.new, purge_interval: PURGE_INTERVAL, purge_timeout: PURGE_TIMEOUT, purge_ttl: PURGE_TTL) ⇒ FileCache

Returns a new instance of FileCache.



23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
# File 'lib/bolt_server/file_cache.rb', line 23

def initialize(config,
               executor: Concurrent::SingleThreadExecutor.new,
               purge_interval: PURGE_INTERVAL,
               purge_timeout: PURGE_TIMEOUT,
               purge_ttl: PURGE_TTL)
  @executor = executor
  @cache_dir = config['cache-dir']
  @config = config
  @logger = Logging.logger[self]
  @cache_dir_mutex = Concurrent::ReadWriteLock.new

  @purge = Concurrent::TimerTask.new(execution_interval: purge_interval,
                                     timeout_interval: purge_timeout,
                                     run_now: true) { expire(purge_ttl) }
  @purge.execute
end

Instance Method Details

#check_file(file_path, sha) ⇒ Object



103
104
105
# File 'lib/bolt_server/file_cache.rb', line 103

def check_file(file_path, sha)
  File.exist?(file_path) && Digest::SHA256.file(file_path) == sha
end

#clientObject



58
59
60
61
62
63
64
65
66
67
68
69
70
71
# File 'lib/bolt_server/file_cache.rb', line 58

def client
  @client ||= begin
                uri = URI(@config['file-server-uri'])
                https = Net::HTTP.new(uri.host, uri.port)
                https.use_ssl = true
                https.ssl_version = :TLSv1_2
                https.ca_file = @config['ssl-ca-cert']
                https.cert = OpenSSL::X509::Certificate.new(ssl_cert)
                https.key = OpenSSL::PKey::RSA.new(ssl_key)
                https.verify_mode = OpenSSL::SSL::VERIFY_PEER
                https.open_timeout = @config['file-server-conn-timeout']
                https
              end
end

#create_cache_dir(sha) ⇒ Object

Create a cache dir if necessary and update it’s last write time. Returns the dir. Acquires @cache_dir_mutex to ensure we don’t try to purge the directory at the same time. Uses the directory mtime because it’s simpler to ensure the directory exists and update mtime in a single place than with a file in a directory that may not exist.



117
118
119
120
121
122
123
124
125
# File 'lib/bolt_server/file_cache.rb', line 117

def create_cache_dir(sha)
  file_dir = File.join(@cache_dir, sha)
  @cache_dir_mutex.with_read_lock do
    # mkdir_p doesn't error if the file exists
    FileUtils.mkdir_p(file_dir, mode: 0o750)
    FileUtils.touch(file_dir)
  end
  file_dir
end

#download_file(file_path, sha, uri) ⇒ Object



127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
# File 'lib/bolt_server/file_cache.rb', line 127

def download_file(file_path, sha, uri)
  if check_file(file_path, sha)
    @logger.debug("File was downloaded while queued: #{file_path}")
    return file_path
  end

  @logger.debug("Downloading file: #{file_path}")

  tmpfile = Tempfile.new(sha, tmppath)
  request_file(uri['path'], uri['params'], tmpfile)

  if Digest::SHA256.file(tmpfile.path) == sha
    # mv doesn't error if the file exists
    FileUtils.mv(tmpfile.path, file_path)
    @logger.debug("Downloaded file: #{file_path}")
    file_path
  else
    msg = "Downloaded file did not match checksum for: #{file_path}"
    @logger.warn msg
    raise Error, msg
  end
end

#expire(purge_ttl) ⇒ Object



165
166
167
168
169
170
171
172
173
174
175
# File 'lib/bolt_server/file_cache.rb', line 165

def expire(purge_ttl)
  expired_time = Time.now - purge_ttl
  @cache_dir_mutex.with_write_lock do
    Dir.glob(File.join(@cache_dir, '*')).select { |f| File.directory?(f) }.each do |dir|
      if (mtime = File.mtime(dir)) < expired_time && dir != tmppath
        @logger.debug("Removing #{dir}, last used at #{mtime}")
        FileUtils.remove_dir(dir)
      end
    end
  end
end

#request_file(path, params, file) ⇒ Object



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
# File 'lib/bolt_server/file_cache.rb', line 73

def request_file(path, params, file)
  uri = "#{@config['file-server-uri'].chomp('/')}#{path}"
  uri = URI(uri)
  uri.query = URI.encode_www_form(params)

  req = Net::HTTP::Get.new(uri)

  begin
    client.request(req) do |resp|
      if resp.code != "200"
        msg = "Failed to download task: #{resp.body}"
        @logger.warn resp.body
        raise Error, msg
      end
      resp.read_body do |chunk|
        file.write(chunk)
      end
    end
  rescue StandardError => e
    if e.is_a?(Bolt::Error)
      raise e
    else
      @logger.warn e
      raise Error, "Failed to download task: #{e.message}"
    end
  end
ensure
  file.close
end

#serial_execute(&block) ⇒ Object



107
108
109
110
111
# File 'lib/bolt_server/file_cache.rb', line 107

def serial_execute(&block)
  promise = Concurrent::Promise.new(executor: @executor, &block).execute.wait
  raise promise.reason if promise.rejected?
  promise.value
end

#setupObject



44
45
46
47
48
# File 'lib/bolt_server/file_cache.rb', line 44

def setup
  FileUtils.mkdir_p(@cache_dir)
  FileUtils.mkdir_p(tmppath)
  self
end

#ssl_certObject



50
51
52
# File 'lib/bolt_server/file_cache.rb', line 50

def ssl_cert
  @ssl_cert ||= File.read(@config['ssl-cert'])
end

#ssl_keyObject



54
55
56
# File 'lib/bolt_server/file_cache.rb', line 54

def ssl_key
  @ssl_key ||= File.read(@config['ssl-key'])
end

#tmppathObject



40
41
42
# File 'lib/bolt_server/file_cache.rb', line 40

def tmppath
  File.join(@cache_dir, 'tmp')
end

#update_file(file_data) ⇒ Object

If the file doesn’t exist or is invalid redownload it This downloads, validates and moves into place



152
153
154
155
156
157
158
159
160
161
162
163
# File 'lib/bolt_server/file_cache.rb', line 152

def update_file(file_data)
  sha = file_data['sha256']
  file_dir = create_cache_dir(file_data['sha256'])
  file_path = File.join(file_dir, File.basename(file_data['filename']))
  if check_file(file_path, sha)
    @logger.debug("Using prexisting task file: #{file_path}")
    return file_path
  end

  @logger.debug("Queueing download for: #{file_path}")
  serial_execute { download_file(file_path, sha, file_data['uri']) }
end