summaryrefslogtreecommitdiffstats
path: root/lib/puppet/indirector/terminus.rb
blob: 546f835247e920d3395e3c61f5c2eebd00c498e3 (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
require 'puppet/indirector'
require 'puppet/indirector/indirection'
require 'puppet/util/instance_loader'

# A simple class that can function as the base class for indirected types.
class Puppet::Indirector::Terminus
    require 'puppet/util/docs'
    extend Puppet::Util::Docs

    class << self
        include Puppet::Util::InstanceLoader

        attr_accessor :name, :terminus_type
        attr_reader :abstract_terminus, :indirection

        # Are we an abstract terminus type, rather than an instance with an
        # associated indirection?
        def abstract_terminus?
            abstract_terminus
        end

        # Convert a constant to a short name.
        def const2name(const)
            const.sub(/^[A-Z]/) { |i| i.downcase }.gsub(/[A-Z]/) { |i| "_#{i.downcase}" }.intern
        end

        # Look up the indirection if we were only provided a name.
        def indirection=(name)
            if name.is_a?(Puppet::Indirector::Indirection)
                @indirection = name
            elsif ind = Puppet::Indirector::Indirection.instance(name)
                @indirection = ind
            else
                raise ArgumentError, "Could not find indirection instance #{name} for #{self.name}"
            end
        end

        def indirection_name
            @indirection.name
        end

        # Register our subclass with the appropriate indirection.
        # This follows the convention that our terminus is named after the
        # indirection.
        def inherited(subclass)
            longname = subclass.to_s
            if longname =~ /#<Class/
                raise Puppet::DevError, "Terminus subclasses must have associated constants"
            end
            names = longname.split("::")

            # Convert everything to a lower-case symbol, converting camelcase to underscore word separation.
            name = names.pop.sub(/^[A-Z]/) { |i| i.downcase }.gsub(/[A-Z]/) { |i| "_#{i.downcase}" }.intern

            subclass.name = name

            # Short-circuit the abstract types, which are those that directly subclass
            # the Terminus class.
            if self == Puppet::Indirector::Terminus
                subclass.mark_as_abstract_terminus
                return
            end

            # Set the terminus type to be the name of the abstract terminus type.
            # Yay, class/instance confusion.
            subclass.terminus_type = self.name

            # Our subclass is specifically associated with an indirection.
            raise("Invalid name #{longname}") unless names.length > 0
            indirection_name = names.pop.sub(/^[A-Z]/) { |i| i.downcase }.gsub(/[A-Z]/) { |i| "_#{i.downcase}" }.intern

            if indirection_name == "" or indirection_name.nil?
                raise Puppet::DevError, "Could not discern indirection model from class constant"
            end

            # This will throw an exception if the indirection instance cannot be found.
            # Do this last, because it also registers the terminus type with the indirection,
            # which needs the above information.
            subclass.indirection = indirection_name

            # And add this instance to the instance hash.
            Puppet::Indirector::Terminus.register_terminus_class(subclass)
        end

        # Mark that this instance is abstract.
        def mark_as_abstract_terminus
            @abstract_terminus = true
        end

        def model
            indirection.model
        end

        # Convert a short name to a constant.
        def name2const(name)
            name.to_s.capitalize.sub(/_(.)/) { |i| $1.upcase }
        end

        # Register a class, probably autoloaded.
        def register_terminus_class(klass)
            setup_instance_loading klass.indirection_name
            instance_hash(klass.indirection_name)[klass.name] = klass
        end

        # Return a terminus by name, using the autoloader.
        def terminus_class(indirection_name, terminus_type)
            setup_instance_loading indirection_name
            loaded_instance(indirection_name, terminus_type)
        end

        # Return all terminus classes for a given indirection.
        def terminus_classes(indirection_name)
            setup_instance_loading indirection_name

            # Load them all.
            instance_loader(indirection_name).loadall

            # And return the list of names.
            loaded_instances(indirection_name)
        end

        private

        def setup_instance_loading(type)
            instance_load type, "puppet/indirector/#{type}" unless instance_loading?(type)
        end
    end

    def indirection
        self.class.indirection
    end

    def initialize
        raise Puppet::DevError, "Cannot create instances of abstract terminus types" if self.class.abstract_terminus?
    end

    def model
        self.class.model
    end

    def name
        self.class.name
    end

    def terminus_type
        self.class.terminus_type
    end
end