Class: Etch

Inherits:
Object
  • Object
show all
Defined in:
lib/etch.rb,
lib/etch.rb

Defined Under Namespace

Classes: Client

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(logger, debug_logger) ⇒ Etch

FIXME: I’m not really proud of this, it seems like there ought to be a way to just use one logger. The problem is that on the server we’d like to use Rails.logger for general logging (which is logging to log/production.log), but be able to turn on debug-level logging for individual connections (via the debug parameter sent in the HTTP requests). If we twiddle the log level of Rails.logger then all connections coming in at the same time as the debug connection will also get logged as debug, making the logs confusing. And if the debug connection aborts for some reason we also risk leaving Rails.logger set to debug, flooding the logs. So it seems like we need a seperate logger for debugging. But that just seems wrong somehow. We don’t want to just dup Rails.logger for each connection, even if Logger didn’t immediately blow up we’d probably end up with scrambled logs as simultaneous connections tried to write at the same time. Or maybe that would work, depending on how Ruby and the OS buffer writes to the file?



70
71
72
73
# File 'lib/etch.rb', line 70

def initialize(logger, debug_logger)
  @logger = logger
  @dlogger = debug_logger
end

Class Method Details

.xmllibObject



19
20
21
# File 'lib/etch.rb', line 19

def self.xmllib
  @@xmllib
end

.xmllib=(lib) ⇒ Object



22
23
24
# File 'lib/etch.rb', line 22

def self.xmllib=(lib)
  @@xmllib=lib
end

Instance Method Details

#generate(configdir, facts, request = {}) ⇒ Object

configdir: Directory containing etch configuration facts: facts in the form of Facter.to_hash request: hash with keys of :files and/or :commands

:files => {'/etc/motd' => {:orig => '/path/to/orig', :local_requests => requestdata}}
:commands => {'packages' => {}, 'solaris_ldapclient' => {}}
If the request hash is empty all items are generated and returned.


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
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
# File 'lib/etch.rb', line 81

def generate(configdir, facts, request={})
  @configdir = configdir
  @facts = facts
  @request = request
  @fqdn = @facts['fqdn']

  if !@fqdn
    raise "fqdn fact not supplied"
  end

  if !File.directory?(@configdir)
    raise "Config directory #{@configdir} doesn't exist"
  end
  
  # Set up all the variables that point to various directories within our
  # base directory.
  @sourcebase        = "#{@configdir}/source"
  @commandsbase      = "#{@configdir}/commands"
  @sitelibbase       = "#{@configdir}/sitelibs"
  @config_dtd_file   = "#{@configdir}/config.dtd"
  @commands_dtd_file = "#{@configdir}/commands.dtd"
  
  #
  # These will be loaded on demand so that all-YAML configurations don't require them
  #
  
  @config_dtd = nil
  @commands_dtd = nil
  
  #
  # Load the defaults file which sets defaults for parameters that the
  # users don't specify in their config files.
  #
  
  @defaults = load_defaults
  
  #
  # Load the nodes file
  #

  groups = Set.new
  @nodes, nodesfile = load_nodes
  # Extract the groups for this node
  if @nodes[@fqdn]
    @nodes[@fqdn].each{|group| groups << group}
  else
    @logger.warn "No entry found for node #{@fqdn} in #{nodesfile}"
    # Some folks might want to terminate here
    #raise "No entry found for node #{@fqdn} in #{nodesfile}"
  end
  @dlogger.debug "Native groups for node #{@fqdn}: #{groups.sort.join(',')}"

  #
  # Load the node groups file
  #

  @group_hierarchy = load_nodegroups

  # Fill out the list of groups for this node with any parent groups
  parents = Set.new
  groups.each do |group|
    parents.merge get_parent_nodegroups(group)
  end
  parents.each{|parent| groups << parent}
  @dlogger.debug "Added groups for node #{@fqdn} due to node group hierarchy: #{parents.sort.join(',')}"

  #
  # Run the external node grouper
  #

  externals = Set.new
  IO.popen(File.join(@configdir, 'nodegrouper') + ' ' + @fqdn) do |pipe|
    pipe.each{|group| externals << group.chomp}
  end
  if !$?.success?
    raise "External node grouper #{File.join(@configdir, 'nodegrouper')} exited with error #{$?.exitstatus}"
  end
  groups.merge externals
  @dlogger.debug "Added groups for node #{@fqdn} due to external node grouper: #{externals.sort.join(',')}"

  @groups = groups.sort
  @dlogger.debug "Total groups for node #{@fqdn}: #{@groups.join(',')}"

  #
  # Build up a list of files to generate, either from the request or from
  # the source repository if the request is for all files
  #

  filelist = []
  if request.empty?
    @dlogger.debug "Building complete file list for request from #{@fqdn}"
    if File.exist?(@sourcebase)
      Find.find(@sourcebase) do |path|
        if File.directory?(path) &&
           (File.exist?(File.join(path, 'config.xml')) ||
            File.exist?(File.join(path, 'config.yml')))
          # Strip @sourcebase from start of path
          filelist << path.sub(Regexp.new('\A' + Regexp.escape(@sourcebase)), '')
        end
      end
    end
  elsif request[:files]
    @dlogger.debug "Building file list based on request for specific files from #{@fqdn}"
    filelist = request[:files].keys
  end
  @dlogger.debug "Generating #{filelist.length} files"

  #
  # Loop over each file in the request and generate it
  #

  @filestack = {}
  @already_generated = {}
  @generation_status = {}
  @configs = {}
  @need_orig = []
  @commands = {}
  @retrycommands = {}
  
  filelist.each do |file|
    @dlogger.debug "Generating #{file}"
    generate_file(file, request)
  end
  
  #
  # Generate configuration commands
  #
  
  commandnames = []
  if request.empty?
    @dlogger.debug "Building complete configuration commands for request from #{@fqdn}"
    if File.exist?(@commandsbase)
      Find.find(@commandsbase) do |path|
        if File.directory?(path) &&
           (File.exist?(File.join(path, 'commands.yml')) ||
            File.exist?(File.join(path, 'commands.xml')))
          commandnames << File.basename(path)
        end
      end
    end
  elsif request[:commands]
    @dlogger.debug "Building commands based on request for specific commands from #{@fqdn}"
    commandnames = request[:commands].keys
  end
  @dlogger.debug "Generating #{commandnames.length} commands"
  commandnames.each do |commandname|
    @dlogger.debug "Generating command #{commandname}"
    generate_commands(commandname, request)
  end
  
  #
  # Returned our assembled results
  #
  
  {:configs => @configs,
   :need_orig => @need_orig,
   :commands => @commands,
   :retrycommands => @retrycommands}
end