summaryrefslogtreecommitdiffstats
path: root/lib/puppet/transaction/resource_harness.rb
blob: d6076681afc1dc726d412d77d5262be11a69c322 (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
require 'puppet/resource/status'

class Puppet::Transaction::ResourceHarness
    extend Forwardable
    def_delegators :@transaction, :relationship_graph

    attr_reader :transaction

    def allow_changes?(resource)
        return true unless resource.purging? and resource.deleting?
        return true unless deps = relationship_graph.dependents(resource) and ! deps.empty? and deps.detect { |d| ! d.deleting? }

        deplabel = deps.collect { |r| r.ref }.join(",")
        plurality = deps.length > 1 ? "":"s"
        resource.warning "#{deplabel} still depend#{plurality} on me -- not purging"
        return false
    end

    def apply_changes(status, changes)
        changes.each do |change|
            status << change.apply

            if change.auditing?
                cache(change.property.resource, change.property.name, change.is)
            end
        end
        status.changed = true
    end

    # Used mostly for scheduling at this point.
    def cached(resource, name)
        Puppet::Util::Storage.cache(resource)[name]
    end

    # Used mostly for scheduling at this point.
    def cache(resource, name, value)
        Puppet::Util::Storage.cache(resource)[name] = value
    end

    def changes_to_perform(status, resource)
        current = resource.retrieve_resource

        cache resource, :checked, Time.now

        return [] if ! allow_changes?(resource)

        audited = copy_audited_parameters(resource, current)

        if param = resource.parameter(:ensure)
            return [] if absent_and_not_being_created?(current, param)
            return [Puppet::Transaction::Change.new(param, current[:ensure])] unless ensure_is_insync?(current, param)
            return [] if ensure_should_be_absent?(current, param)
        end

        resource.properties.reject { |p| p.name == :ensure }.reject do |param|
            param.should.nil?
        end.reject do |param|
            param_is_insync?(current, param)
        end.collect do |param|
            change = Puppet::Transaction::Change.new(param, current[param.name])
            change.auditing = true if audited.include?(param.name)
            change
        end
    end

    def copy_audited_parameters(resource, current)
        return [] unless audit = resource[:audit]
        audit = Array(audit).collect { |p| p.to_sym }
        audited = []
        audit.find_all do |param|
            next if resource[param]

            if value = cached(resource, param)
                resource[param] = value
                audited << param
            else
                resource.info "Storing newly-audited value #{current[param]} for #{param}"
                cache(resource, param, current[param])
            end
        end

        audited
    end

    def evaluate(resource)
        start = Time.now
        status = Puppet::Resource::Status.new(resource)

        if changes = changes_to_perform(status, resource) and ! changes.empty?
            status.out_of_sync = true
            status.change_count = changes.length
            apply_changes(status, changes)
            if ! resource.noop?
                cache(resource, :synced, Time.now)
                resource.flush if resource.respond_to?(:flush)
            end
        end
        return status
    rescue => detail
        resource.fail "Could not create resource status: #{detail}" unless status
        puts detail.backtrace if Puppet[:trace]
        resource.err "Could not evaluate: #{detail}"
        status.failed = true
        return status
    ensure
        (status.evaluation_time = Time.now - start) if status
    end

    def initialize(transaction)
        @transaction = transaction
    end

    def scheduled?(status, resource)
        return true if Puppet[:ignoreschedules]
        return true unless schedule = schedule(resource)

        # We use 'checked' here instead of 'synced' because otherwise we'll
        # end up checking most resources most times, because they will generally
        # have been synced a long time ago (e.g., a file only gets updated
        # once a month on the server and its schedule is daily; the last sync time
        # will have been a month ago, so we'd end up checking every run).
        return schedule.match?(cached(resource, :checked).to_i)
    end

    def schedule(resource)
        unless resource.catalog
            resource.warning "Cannot schedule without a schedule-containing catalog"
            return nil
        end

        return nil unless name = resource[:schedule]
        resource.catalog.resource(:schedule, name) || resource.fail("Could not find schedule #{name}")
    end

    private

    def absent_and_not_being_created?(current, param)
        current[:ensure] == :absent and param.should.nil?
    end

    def ensure_is_insync?(current, param)
        param.insync?(current[:ensure])
    end

    def ensure_should_be_absent?(current, param)
        param.should == :absent
    end

    def param_is_insync?(current, param)
        param.insync?(current[param.name])
    end
end