summaryrefslogtreecommitdiffstats
path: root/lib/puppet/indirector/terminus.rb
blob: 4ebd0d004e39f7aec58b8500bf3abccf1c423b4b (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