summaryrefslogtreecommitdiff
path: root/lib/oxidized/node.rb
blob: 300221ec4d5790edf6181c579b94e26be16c73fc (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
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
240
241
242
243
244
245
246
247
248
module Oxidized
  require 'resolv'
  require 'ostruct'
  require_relative 'node/stats'
  class MethodNotFound < OxidizedError; end
  class ModelNotFound  < OxidizedError; end
  class Node
    attr_reader :name, :ip, :model, :input, :output, :group, :auth, :prompt, :vars, :last, :repo
    attr_accessor :running, :user, :email, :msg, :from, :stats, :retry
    alias :running? :running

    def initialize opt
      Oxidized.logger.debug 'resolving DNS for %s...' % opt[:name]
      # remove the prefix if an IP Address is provided with one as IPAddr converts it to a network address.
      ip_addr, _ = opt[:ip].to_s.split("/")
      Oxidized.logger.debug 'IPADDR %s' % ip_addr.to_s
      @name           = opt[:name]
      @ip             = IPAddr.new(ip_addr).to_s rescue nil
      @ip           ||= Resolv.new.getaddress(@name) if Oxidized.config.resolve_dns?
      @ip           ||= @name
      @group          = opt[:group]
      @model          = resolve_model opt
      @input          = resolve_input opt
      @output         = resolve_output opt
      @auth           = resolve_auth opt
      @prompt         = resolve_prompt opt
      @vars           = opt[:vars]
      @stats          = Stats.new
      @retry          = 0
      @repo           = resolve_repo opt

      # model instance needs to access node instance
      @model.node = self
    end

    def run
      status, config = :fail, nil
      @input.each do |input|
        # don't try input if model is missing config block, we may need strong config to class_name map
        cfg_name = input.to_s.split('::').last.downcase
        next unless @model.cfg[cfg_name] and not @model.cfg[cfg_name].empty?
        @model.input = input = input.new
        if config = run_input(input)
          Oxidized.logger.debug "lib/oxidized/node.rb: #{input.class.name} ran for #{name} successfully"
          status = :success
          break
        else
          Oxidized.logger.debug "lib/oxidized/node.rb: #{input.class.name} failed for #{name}"
          status = :no_connection
        end
      end
      @model.input = nil
      [status, config]
    end

    def run_input input
      rescue_fail = {}
      [input.class::RescueFail, input.class.superclass::RescueFail].each do |hash|
        hash.each do |level, errors|
          errors.each do |err|
            rescue_fail[err] = level
          end
        end
      end
      begin
        input.connect(self) and input.get
      rescue *rescue_fail.keys => err
        resc = ''
        if not level = rescue_fail[err.class]
          resc  = err.class.ancestors.find { |e| rescue_fail.keys.include? e }
          level = rescue_fail[resc]
          resc  = " (rescued #{resc})"
        end
        Oxidized.logger.send(level, '%s raised %s%s with msg "%s"' % [self.ip, err.class, resc, err.message])
        return false
      rescue => err
        file = Oxidized::Config::Crash + '.' + self.ip.to_s
        open file, 'w' do |fh|
          fh.puts Time.now.utc
          fh.puts err.message + ' [' + err.class.to_s + ']'
          fh.puts '-' * 50
          fh.puts err.backtrace
        end
        Oxidized.logger.error '%s raised %s with msg "%s", %s saved' % [self.ip, err.class, err.message, file]
        return false
      end
    end

    def serialize
      h = {
        :name      => @name,
        :full_name => @name,
        :ip        => @ip,
        :group     => @group,
        :model     => @model.class.to_s,
        :last      => nil,
        :vars      => @vars,
        :mtime     => @stats.mtime,
      }
      h[:full_name] = [@group, @name].join('/') if @group
      if @last
        h[:last] = {
          :start  => @last.start,
          :end    => @last.end,
          :status => @last.status,
          :time   => @last.time,
        }
      end
      h
    end

    def last= job
      if job
        ostruct = OpenStruct.new
        ostruct.start  = job.start
        ostruct.end    = job.end
        ostruct.status = job.status
        ostruct.time   = job.time
        @last = ostruct
      else
        @last = nil
      end
    end

    def reset
      @user = @email = @msg = @from = nil
      @retry = 0
    end

    def modified
      @stats.update_mtime
    end

    private

    def resolve_prompt opt
      opt[:prompt] || @model.prompt || Oxidized.config.prompt
    end

    def resolve_auth opt
      # Resolve configured username/password
      {
        username:       resolve_key(:username, opt),
        password:       resolve_key(:password, opt),
      }
    end

    def resolve_input opt
      inputs = resolve_key :input, opt, Oxidized.config.input.default
      inputs.split(/\s*,\s*/).map do |input|
        if not Oxidized.mgr.input[input]
          Oxidized.mgr.add_input input or raise MethodNotFound, "#{input} not found for node #{ip}"
        end
        Oxidized.mgr.input[input]
      end
    end

    def resolve_output opt
      output = resolve_key :output, opt, Oxidized.config.output.default
      if not Oxidized.mgr.output[output]
        Oxidized.mgr.add_output output or raise MethodNotFound, "#{output} not found for node #{ip}"
      end
      Oxidized.mgr.output[output]
    end

    def resolve_model opt
      model = resolve_key :model, opt
      if not Oxidized.mgr.model[model]
        Oxidized.logger.debug "lib/oxidized/node.rb: Loading model #{model.inspect}"
        Oxidized.mgr.add_model model or raise ModelNotFound, "#{model} not found for node #{ip}"
      end
      Oxidized.mgr.model[model].new
    end

    def resolve_repo opt
      if is_git? opt
        remote_repo = Oxidized.config.output.git.repo

        if remote_repo.is_a?(::String)
          if Oxidized.config.output.git.single_repo? || @group.nil?
            remote_repo
          else
            File.join(File.dirname(remote_repo), @group + '.git')
          end
        else
          remote_repo[@group]
        end
      elsif is_gitcrypt? opt
        remote_repo = Oxidized.config.output.gitcrypt.repo

        if remote_repo.is_a?(::String)
          if Oxidized.config.output.gitcrypt.single_repo? || @group.nil?
            remote_repo
          else
            File.join(File.dirname(remote_repo), @group + '.git')
          end
        else
          remote_repo[@group]
        end
      else
        return
      end
    end

    def resolve_key key, opt, global = nil
      # resolve key, first get global, then get group then get node config
      key_sym = key.to_sym
      key_str = key.to_s
      value   = global
      Oxidized.logger.debug "node.rb: resolving node key '#{key}', with passed global value of '#{value}' and node value '#{opt[key_sym]}'"

      # global
      if not value and Oxidized.config.has_key?(key_str)
        value = Oxidized.config[key_str]
        Oxidized.logger.debug "node.rb: setting node key '#{key}' to value '#{value}' from global"
      end

      # group
      if Oxidized.config.groups.has_key?(@group)
        if Oxidized.config.groups[@group].has_key?(key_str)
          value = Oxidized.config.groups[@group][key_str]
          Oxidized.logger.debug "node.rb: setting node key '#{key}' to value '#{value}' from group"
        end
      end

      # model
      if Oxidized.config.models.has_key?(@model.class.name.to_s.downcase)
        if Oxidized.config.models[@model.class.name.to_s.downcase].has_key?(key_str)
          value = Oxidized.config.models[@model.class.name.to_s.downcase][key_str]
          Oxidized.logger.debug "node.rb: setting node key '#{key}' to value '#{value}' from model"
        end
      end

      # node
      value = opt[key_sym] || value
      Oxidized.logger.debug "node.rb: returning node key '#{key}' with value '#{value}'"
      value
    end

    def is_git? opt
      (opt[:output] || Oxidized.config.output.default) == 'git'
    end

    def is_gitcrypt? opt
      (opt[:output] || Oxidized.config.output.default) == 'gitcrypt'
    end
  end
end