summaryrefslogtreecommitdiffstats
path: root/lib/puppet/indirector/indirection.rb
blob: f464f846f23c5c812ebf7806701b232d2779188a (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
# An actual indirection.
class Puppet::Indirector::Indirection
    @@indirections = []

    # Clear all cached termini from all indirections.
    def self.clear_cache
        @@indirections.each { |ind| ind.clear_cache }
    end

    # Find an indirection by name.  This is provided so that Terminus classes
    # can specifically hook up with the indirections they are associated with.
    def self.instance(name)
        @@indirections.find { |i| i.name == name }
    end
    
    attr_accessor :name, :model

    # Create and return our cache terminus.
    def cache
        raise(Puppet::DevError, "Tried to cache when no cache class was set") unless cache_class
        terminus(cache_class)
    end

    # Should we use a cache?
    def cache?
        cache_class ? true : false
    end

    attr_reader :cache_class
    # Define a terminus class to be used for caching.
    def cache_class=(class_name)
        validate_terminus_class(class_name)
        @cache_class = class_name
    end

    # Clear our cached list of termini, and reset the cache name
    # so it's looked up again.
    # This is only used for testing.
    def clear_cache
        @termini.clear
    end

    # This is only used for testing.
    def delete
        @@indirections.delete(self) if @@indirections.include?(self)
    end

    def initialize(model, name, options = {})
        @model = model
        @name = name

        @termini = {}
        @cache_class = nil

        raise(ArgumentError, "Indirection %s is already defined" % @name) if @@indirections.find { |i| i.name == @name }
        @@indirections << self

        if mod = options[:extend]
            extend(mod)
            options.delete(:extend)
        end

        # This is currently only used for cache_class and terminus_class.
        options.each do |name, value|
            begin
                send(name.to_s + "=", value)
            rescue NoMethodError
                raise ArgumentError, "%s is not a valid Indirection parameter" % name
            end
        end
    end

    # Return the singleton terminus for this indirection.
    def terminus(terminus_name = nil)
        # Get the name of the terminus.
        unless terminus_name ||= terminus_class
            raise Puppet::DevError, "No terminus specified for %s; cannot redirect" % self.name
        end
        
        return @termini[terminus_name] ||= make_terminus(terminus_name)
    end

    attr_reader :terminus_class

    # Specify the terminus class to use.
    def terminus_class=(terminus_class)
        validate_terminus_class(terminus_class)
        @terminus_class = terminus_class
    end

    # This is used by terminus_class= and cache=.
    def validate_terminus_class(terminus_class)
        unless terminus_class and terminus_class.to_s != ""
            raise ArgumentError, "Invalid terminus name %s" % terminus_class.inspect
        end
        unless Puppet::Indirector::Terminus.terminus_class(self.name, terminus_class)
            raise ArgumentError, "Could not find terminus %s for indirection %s" % [terminus_class, self.name]
        end
    end

    def find(key, *args)
        # Select the appropriate terminus if there's a hook
        # for doing so.  This allows the caller to pass in some kind
        # of URI that the indirection can use for routing to the appropriate
        # terminus.
        if respond_to?(:select_terminus)
            terminus_name = select_terminus(key, *args)
        else
            terminus_name = terminus_class
        end

        # See if our instance is in the cache and up to date.
        if cache? and cache.has_most_recent?(key, terminus(terminus_name).version(key))
            Puppet.info "Using cached %s %s" % [self.name, key]
            return cache.find(key, *args)
        end

        # Otherwise, return the result from the terminus, caching if appropriate.
        if result = terminus(terminus_name).find(key, *args)
            result.version ||= Time.now.utc
            if cache?
                Puppet.info "Caching %s %s" % [self.name, key]
                cache.save(result, *args)
            end
            return result
        end
    end

    def destroy(*args)
        terminus.destroy(*args)
    end

    def search(*args)
        terminus.search(*args)
    end

    # these become instance methods 
    def save(instance, *args)
        instance.version ||= Time.now.utc
        dest = cache? ? cache : terminus
        return if dest.has_most_recent?(instance.name, instance.version)
        Puppet.info "Caching %s %s" % [self.name, instance.name] if cache?
        cache.save(instance, *args) if cache?
        terminus.save(instance, *args)
    end

    def version(*args)
        terminus.version(*args)
    end

    private

    # Create a new terminus instance.
    def make_terminus(terminus_class)
        # Load our terminus class.
        unless klass = Puppet::Indirector::Terminus.terminus_class(self.name, terminus_class)
            raise ArgumentError, "Could not find terminus %s for indirection %s" % [terminus_class, self.name]
        end
        return klass.new
    end
end