diff options
Diffstat (limited to 'lib/chef/checksum_cache.rb')
-rw-r--r-- | lib/chef/checksum_cache.rb | 190 |
1 files changed, 190 insertions, 0 deletions
diff --git a/lib/chef/checksum_cache.rb b/lib/chef/checksum_cache.rb new file mode 100644 index 0000000000..6db7115a56 --- /dev/null +++ b/lib/chef/checksum_cache.rb @@ -0,0 +1,190 @@ +# +# Author:: Adam Jacob (<adam@opscode.com>) +# Author:: Daniel DeLeo (<dan@kallistec.com>) +# Copyright:: Copyright (c) 2009 Opscode, Inc. +# Copyright:: Copyright (c) 2009 Daniel DeLeo +# License:: Apache License, Version 2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +require 'set' +require 'fileutils' +require 'chef/log' +require 'chef/config' +require 'chef/client' +require 'chef/mixin/convert_to_class_name' +require 'singleton' +require 'moneta' + +class Chef + class ChecksumCache + include Chef::Mixin::ConvertToClassName + include ::Singleton + + attr_reader :moneta + + def initialize(*args) + self.reset!(*args) + end + + def reset!(backend=nil, options=nil) + backend ||= Chef::Config[:cache_type] + options ||= Chef::Config[:cache_options] + + begin + require "moneta/#{convert_to_snake_case(backend, 'Moneta')}" + require 'chef/monkey_patches/moneta' + rescue LoadError => e + Chef::Log.fatal("Could not load Moneta back end #{backend.inspect}") + raise e + end + + @moneta = Moneta.const_get(backend).new(options) + end + + def self.reset_cache_validity + @valid_cached_checksums = nil + end + + Chef::Client.when_run_starts do |run_status| + reset_cache_validity + end + + def self.valid_cached_checksums + @valid_cached_checksums ||= Set.new + end + + def self.validate_checksum(checksum_key) + valid_cached_checksums << checksum_key + end + + def self.all_cached_checksums + all_checksums_with_filenames = {} + + Dir[File.join(Chef::Config[:cache_options][:path], '*')].each do |cksum_file| + all_checksums_with_filenames[File.basename(cksum_file)] = cksum_file + end + all_checksums_with_filenames + end + + def self.cleanup_checksum_cache + Chef::Log.debug("Cleaning the checksum cache") + if (Chef::Config[:cache_type].to_s == "BasicFile") + all_cached_checksums.each do |cache_key, cksum_cache_file| + unless valid_cached_checksums.include?(cache_key) + remove_unused_checksum(cksum_cache_file) + end + end + end + end + + Chef::Client.when_run_completes_successfully do |run_status| + cleanup_checksum_cache + end + + def self.remove_unused_checksum(checksum_file) + Chef::Log.debug("Removing unused checksum cache file #{checksum_file}") + FileUtils.rm(checksum_file) + end + + def self.checksum_for_file(*args) + instance.checksum_for_file(*args) + end + + def validate_checksum(*args) + self.class.validate_checksum(*args) + end + + def checksum_for_file(file, key=nil) + key ||= generate_key(file) + fstat = File.stat(file) + lookup_checksum(key, fstat) || generate_checksum(key, file, fstat) + end + + def lookup_checksum(key, fstat) + cached = fetch(key) + if cached && file_unchanged?(cached, fstat) + validate_checksum(key) + cached["checksum"] + else + nil + end + end + + def generate_checksum(key, file, fstat) + checksum = checksum_file(file, Digest::SHA256.new) + moneta.store(key, {"mtime" => fstat.mtime.to_f, "checksum" => checksum}) + validate_checksum(key) + checksum + end + + def generate_key(file, group="chef") + "#{group}-file-#{file.gsub(/(#{File::SEPARATOR}|\.)/, '-')}" + end + + def self.generate_md5_checksum_for_file(*args) + instance.generate_md5_checksum_for_file(*args) + end + + def generate_md5_checksum_for_file(file) + checksum_file(file, Digest::MD5.new) + end + + def generate_md5_checksum(io) + checksum_io(io, Digest::MD5.new) + end + + private + + def fetch(key) + @moneta.fetch(key) + rescue ArgumentError => e + Log.warn "Error loading cached checksum for key #{key.inspect}" + Log.warn(e) + repair_checksum_cache + nil + end + + def repair_checksum_cache + Chef::Log.info("Removing invalid checksum cache files") + Dir["#{Chef::Config[:cache_options][:path]}/*"].each do |file_path| + File.unlink(file_path) unless File.size?(file_path) + end + end + + def file_unchanged?(cached, fstat) + cached["mtime"].to_f == fstat.mtime.to_f + end + + def checksum_file(file, digest) + File.open(file, 'rb') { |f| checksum_io(f, digest) } + end + + def checksum_io(io, digest) + while chunk = io.read(1024 * 8) + digest.update(chunk) + end + digest.hexdigest + end + + end +end + +module Moneta + module Defaults + def default + nil + end + end +end |