File: upstart.rb

package info (click to toggle)
puppet-agent 7.23.0-1
  • links: PTS, VCS
  • area: main
  • in suites: bookworm
  • size: 19,092 kB
  • sloc: ruby: 245,074; sh: 456; makefile: 38; xml: 33
file content (385 lines) | stat: -rw-r--r-- 10,742 bytes parent folder | download
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
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
Puppet::Type.type(:service).provide :upstart, :parent => :debian do
  START_ON = /^\s*start\s+on/
  COMMENTED_START_ON = /^\s*#+\s*start\s+on/
  MANUAL   = /^\s*manual\s*$/

  desc "Ubuntu service management with `upstart`.

  This provider manages `upstart` jobs on Ubuntu. For `upstart` documentation,
  see <http://upstart.ubuntu.com/>.
  "

  confine :any => [
    Puppet.runtime[:facter].value(:operatingsystem) == 'Ubuntu',
    (Puppet.runtime[:facter].value(:osfamily) == 'RedHat' and Puppet.runtime[:facter].value(:operatingsystemrelease) =~ /^6\./),
    (Puppet.runtime[:facter].value(:operatingsystem) == 'Amazon' and Puppet.runtime[:facter].value(:operatingsystemmajrelease) =~ /\d{4}/),
    Puppet.runtime[:facter].value(:operatingsystem) == 'LinuxMint',
  ]

  defaultfor :operatingsystem => :ubuntu, :operatingsystemmajrelease => ["10.04", "12.04", "14.04", "14.10"]
  defaultfor :operatingsystem => :LinuxMint, :operatingsystemmajrelease => ["10", "11", "12", "13", "14", "15", "16", "17"]

  commands :start   => "/sbin/start",
           :stop    => "/sbin/stop",
           :restart => "/sbin/restart",
           :status_exec  => "/sbin/status",
           :initctl => "/sbin/initctl"

  # We only want to use upstart as our provider if the upstart daemon is running.
  # This can be checked by running `initctl version --quiet` on a machine that has
  # upstart installed.
  confine :true => lambda { has_initctl? }

  def self.has_initctl?
    # Puppet::Util::Execution.execute does not currently work on jRuby.
    # Unfortunately, since this confine is invoked whenever we check for
    # provider suitability and since provider suitability is still checked
    # on the master, this confine will still be invoked on the master. Thus
    # to avoid raising an exception, we do an early return if we're running
    # on jRuby.
    return false if Puppet::Util::Platform.jruby?

    begin
      initctl('version', '--quiet')
      true
    rescue
      false
    end
  end

  # upstart developer haven't implemented initctl enable/disable yet:
  # http://www.linuxplanet.com/linuxplanet/tutorials/7033/2/
  has_feature :enableable

  def self.instances
    self.get_services(self.excludes) # Take exclude list from init provider
  end

  def self.excludes
    excludes = super
    if Puppet.runtime[:facter].value(:osfamily) == 'RedHat'
      # Puppet cannot deal with services that have instances, so we have to
      # ignore these services using instances on redhat based systems.
      excludes += %w[serial tty]
    end

    excludes
  end


  def self.get_services(exclude=[])
    instances = []
    execpipe("#{command(:initctl)} list") { |process|
      process.each_line { |line|
        # needs special handling of services such as network-interface:
        # initctl list:
        # network-interface (lo) start/running
        # network-interface (eth0) start/running
        # network-interface-security start/running
        matcher = line.match(/^(network-interface)\s\(([^\)]+)\)/)
        name = if matcher
                 "#{matcher[1]} INTERFACE=#{matcher[2]}"
               else
                 matcher = line.match(/^(network-interface-security)\s\(([^\)]+)\)/)
                 if matcher
                   "#{matcher[1]} JOB=#{matcher[2]}"
                 else
                   line.split.first
                 end
               end
        instances << new(:name => name)
      }
    }
    instances.reject { |instance| exclude.include?(instance.name) }
  end

  def self.defpath
    ["/etc/init", "/etc/init.d"]
  end

  def upstart_version
    @upstart_version ||= initctl("--version").match(/initctl \(upstart ([^\)]*)\)/)[1]
  end

  # Where is our override script?
  def overscript
    @overscript ||= initscript.gsub(/\.conf$/,".override")
  end

  def search(name)
    # Search prefers .conf as that is what upstart uses
    [".conf", "", ".sh"].each do |suffix|
      paths.each do |path|
        service_name = name.match(/^(\S+)/)[1]
        fqname = File.join(path, service_name + suffix)
        if Puppet::FileSystem.exist?(fqname)
          return fqname
        end

        self.debug("Could not find #{name}#{suffix} in #{path}")
      end
    end

    raise Puppet::Error, "Could not find init script or upstart conf file for '#{name}'"
  end

  def enabled?
    return super if not is_upstart?

    script_contents = read_script_from(initscript)
    if version_is_pre_0_6_7
      enabled_pre_0_6_7?(script_contents)
    elsif version_is_pre_0_9_0
      enabled_pre_0_9_0?(script_contents)
    elsif version_is_post_0_9_0
      enabled_post_0_9_0?(script_contents, read_override_file)
    end
  end

  def enable
    return super if not is_upstart?

    script_text = read_script_from(initscript)
    if version_is_pre_0_9_0
      enable_pre_0_9_0(script_text)
    else
      enable_post_0_9_0(script_text, read_override_file)
    end
  end

  def disable
    return super if not is_upstart?

    script_text = read_script_from(initscript)
    if version_is_pre_0_6_7
      disable_pre_0_6_7(script_text)
    elsif version_is_pre_0_9_0
      disable_pre_0_9_0(script_text)
    elsif version_is_post_0_9_0
      disable_post_0_9_0(read_override_file)
    end
  end

  def startcmd
    is_upstart? ? [command(:start), @resource[:name]] : super
  end

  def stopcmd
    is_upstart? ? [command(:stop),  @resource[:name]] : super
  end

  def restartcmd
    is_upstart? ? (@resource[:hasrestart] == :true) && [command(:restart), @resource[:name]] : super
  end

  def statuscmd
    is_upstart? ? nil : super #this is because upstart is broken with its return codes
  end

  def status
    if (@resource[:hasstatus] == :false) ||
        @resource[:status] ||
        ! is_upstart?
      return super
    end

    output = status_exec(@resource[:name].split)
    if output =~ /start\//
      return :running
    else
      return :stopped
    end
  end

private
  def is_upstart?(script = initscript)
    Puppet::FileSystem.exist?(script) && script.match(/\/etc\/init\/\S+\.conf/)
  end

  def version_is_pre_0_6_7
    Puppet::Util::Package.versioncmp(upstart_version, "0.6.7") == -1
  end

  def version_is_pre_0_9_0
    Puppet::Util::Package.versioncmp(upstart_version, "0.9.0") == -1
  end

  def version_is_post_0_9_0
    Puppet::Util::Package.versioncmp(upstart_version, "0.9.0") >= 0
  end

  def enabled_pre_0_6_7?(script_text)
    # Upstart version < 0.6.7 means no manual stanza.
    if script_text.match(START_ON)
      return :true
    else
      return :false
    end
  end

  def enabled_pre_0_9_0?(script_text)
    # Upstart version < 0.9.0 means no override files
    # So we check to see if an uncommented start on or manual stanza is the last one in the file
    # The last one in the file wins.
    enabled = :false
    script_text.each_line do |line|
      if line.match(START_ON)
        enabled = :true
      elsif line.match(MANUAL)
        enabled = :false
      end
    end
    enabled
  end

  def enabled_post_0_9_0?(script_text, over_text)
    # This version has manual stanzas and override files
    # So we check to see if an uncommented start on or manual stanza is the last one in the
    # conf file and any override files. The last one in the file wins.
    enabled = :false

    script_text.each_line do |line|
      if line.match(START_ON)
        enabled = :true
      elsif line.match(MANUAL)
        enabled = :false
      end
    end
    over_text.each_line do |line|
      if line.match(START_ON)
        enabled = :true
      elsif line.match(MANUAL)
        enabled = :false
      end
    end if over_text
    enabled
  end

  def enable_pre_0_9_0(text)
    # We also need to remove any manual stanzas to ensure that it is enabled
    text = remove_manual_from(text)

    if enabled_pre_0_9_0?(text) == :false
      enabled_script =
        if text.match(COMMENTED_START_ON)
          uncomment_start_block_in(text)
        else
          add_default_start_to(text)
        end
    else
      enabled_script = text
    end

    write_script_to(initscript, enabled_script)
  end

  def enable_post_0_9_0(script_text, over_text)
    over_text = remove_manual_from(over_text)

    if enabled_post_0_9_0?(script_text, over_text) == :false
      if script_text.match(START_ON)
        over_text << extract_start_on_block_from(script_text)
      else
        over_text << "\nstart on runlevel [2,3,4,5]"
      end
    end

    write_script_to(overscript, over_text)
  end

  def disable_pre_0_6_7(script_text)
    disabled_script = comment_start_block_in(script_text)
    write_script_to(initscript, disabled_script)
  end

  def disable_pre_0_9_0(script_text)
    write_script_to(initscript, ensure_disabled_with_manual(script_text))
  end

  def disable_post_0_9_0(over_text)
    write_script_to(overscript, ensure_disabled_with_manual(over_text))
  end

  def read_override_file
    if Puppet::FileSystem.exist?(overscript)
      read_script_from(overscript)
    else
      ""
    end
  end

  def uncomment(line)
    line.gsub(/^(\s*)#+/, '\1')
  end

  def remove_trailing_comments_from_commented_line_of(line)
    line.gsub(/^(\s*#+\s*[^#]*).*/, '\1')
  end

  def remove_trailing_comments_from(line)
    line.gsub(/^(\s*[^#]*).*/, '\1')
  end

  def unbalanced_parens_on(line)
    line.count('(') - line.count(')')
  end

  def remove_manual_from(text)
    text.gsub(MANUAL, "")
  end

  def comment_start_block_in(text)
    parens = 0
    text.lines.map do |line|
      if line.match(START_ON) || parens > 0
        # If there are more opening parens than closing parens, we need to comment out a multiline 'start on' stanza
        parens += unbalanced_parens_on(remove_trailing_comments_from(line))
        "#" + line
      else
        line
      end
    end.join('')
  end

  def uncomment_start_block_in(text)
    parens = 0
    text.lines.map do |line|
      if line.match(COMMENTED_START_ON) || parens > 0
        parens += unbalanced_parens_on(remove_trailing_comments_from_commented_line_of(line))
        uncomment(line)
      else
        line
      end
    end.join('')
  end

  def extract_start_on_block_from(text)
    parens = 0
    text.lines.map do |line|
      if line.match(START_ON) || parens > 0
        parens += unbalanced_parens_on(remove_trailing_comments_from(line))
        line
      end
    end.join('')
  end

  def add_default_start_to(text)
    text + "\nstart on runlevel [2,3,4,5]"
  end

  def ensure_disabled_with_manual(text)
    remove_manual_from(text) + "\nmanual"
  end

  def read_script_from(filename)
    File.open(filename) do |file|
      file.read
    end
  end

  def write_script_to(file, text)
    Puppet::Util.replace_file(file, 0644) do |f|
      f.write(text)
    end
  end
end