summaryrefslogtreecommitdiff
path: root/chef/lib/chef/checksum.rb
blob: 5a45e760079c06905624ea565e1dc328c20d7f09 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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
54
55
56
57
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
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
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
#
# Author:: Tim Hinderliter (<tim@opscode.com>)
# Copyright:: Copyright (c) 2010 Opscode, Inc.
# 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 'chef/log'
require 'uuidtools'

class Chef
  # == Chef::Checksum
  # Checksum for an individual file; e.g., used for sandbox/cookbook uploading
  # to track which files the system already manages.
  class Checksum
    attr_accessor :checksum, :create_time
    attr_accessor :couchdb_id, :couchdb_rev

    # When a Checksum commits a sandboxed file to its final home in the checksum
    # repo, this attribute will have the original on-disk path where the file
    # was stored; it will be used if the commit is reverted to restore the sandbox
    # to the pre-commit state.
    attr_reader :original_committed_file_location

    DESIGN_DOCUMENT = {
      "version" => 1,
      "language" => "javascript",
      "views" => {
        "all" => {
          "map" => <<-EOJS
          function(doc) { 
            if (doc.chef_type == "checksum") {
              emit(doc.checksum, doc);
            }
          }
          EOJS
        },
      }
    }
    
    # Creates a new Chef::Checksum object.
    # === Arguments
    # checksum::: the MD5 content hash of the file
    # couchdb::: An instance of Chef::CouchDB
    #
    # === Returns
    # object<Chef::Checksum>:: Duh. :)
    def initialize(checksum=nil, couchdb=nil)
      @create_time = Time.now.iso8601
      @checksum = checksum
      @original_committed_file_location = nil
    end
    
    def to_json(*a)
      result = {
        :checksum => checksum,
        :create_time => create_time,
        :json_class => self.class.name,
        :chef_type => 'checksum',

        # For Chef::CouchDB (id_to_name, name_to_id)
        :name => checksum
      }
      result.to_json(*a)
    end

    def self.json_create(o)
      checksum = new(o['checksum'])
      checksum.create_time = o['create_time']

      if o.has_key?('_rev')
        checksum.couchdb_rev = o["_rev"]
        o.delete("_rev")
      end
      if o.has_key?("_id")
        checksum.couchdb_id = o["_id"]
        o.delete("_id")
      end
      checksum
    end


    ##
    # On-Disk Checksum File Repo (Chef Server API)
    ##

    def file_location
      File.join(checksum_repo_directory, checksum)
    end

    def checksum_repo_directory
      File.join(Chef::Config.checksum_path, checksum[0..1])
    end

    # Moves the given +sandbox_file+ into the checksum repo using the path
    # given by +file_location+ and saves the Checksum to the database
    def commit_sandbox_file(sandbox_file)
      @original_committed_file_location = sandbox_file
      Chef::Log.info("Commiting sandbox file: move #{sandbox_file} to #{file_location}")
      FileUtils.mkdir_p(checksum_repo_directory)
      File.rename(sandbox_file, file_location)
      cdb_save
    end

    # Moves the checksum file back to its pre-commit location and deletes
    # the checksum object from the database, effectively undoing +commit_sandbox_file+.
    # Raises Chef::Exceptions::IllegalChecksumRevert if the original file location
    # is unknown, which is will be the case if commit_sandbox_file was not
    # previously called
    def revert_sandbox_file_commit
      unless original_committed_file_location
        raise Chef::Exceptions::IllegalChecksumRevert, "Checksum #{self.inspect} cannot be reverted because the original sandbox file location is not known"
      end

      Chef::Log.warn("Reverting sandbox file commit: moving #{file_location} back to #{original_committed_file_location}")
      File.rename(file_location, original_committed_file_location)
      cdb_destroy
    end

    # Removes the on-disk file backing this checksum object, then removes it
    # from the database
    def purge
      purge_file
      cdb_destroy
    end

    ##
    # Couchdb
    ##

    def self.create_design_document(couchdb=nil)
      (couchdb || Chef::CouchDB.new).create_design_document("checksums", DESIGN_DOCUMENT)
    end
    
    def self.cdb_list(inflate=false, couchdb=nil)
      rs = (couchdb || Chef::CouchDB.new).list("checksums", inflate)
      lookup = (inflate ? "value" : "key")
      rs["rows"].collect { |r| r[lookup] }        
    end
    
    def self.cdb_all_checksums(couchdb = nil)
      rs = (couchdb || Chef::CouchDB.new).list("checksums", true)
      rs["rows"].inject({}) { |hash_result, r| hash_result[r['key']] = 1; hash_result }
    end

    def self.cdb_load(checksum, couchdb=nil)
      # Probably want to look for a view here at some point
      (couchdb || Chef::CouchDB.new).load("checksum", checksum)
    end

    def cdb_destroy(couchdb=nil)
      (couchdb || Chef::CouchDB.new).delete("checksum", checksum, @couchdb_rev)
    end

    def cdb_save(couchdb=nil)
      @couchdb_rev = (couchdb || Chef::CouchDB.new).store("checksum", checksum, self)["rev"]
    end


    private

    # Deletes the file backing this checksum from the on-disk repo.
    # Purging the checksums is how users can get back to a valid state if
    # they've deleted files, so we silently swallow Errno::ENOENT here.
    def purge_file
      FileUtils.rm(file_location)
    rescue Errno::ENOENT
      true
    end

  end
end