aws-codedeploy-agent 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (84) hide show
  1. data/.gitignore +2 -0
  2. data/CHANGES.md +3 -0
  3. data/Gemfile +13 -0
  4. data/LICENSE +177 -0
  5. data/NOTICE +2 -0
  6. data/README.md +16 -0
  7. data/aws-codedeploy-agent.gemspec +39 -0
  8. data/bin/codedeploy-agent +78 -0
  9. data/bin/codedeploy-install +15 -0
  10. data/bin/codedeploy-uninstall +13 -0
  11. data/certs/host-agent-deployment-signer-ca-chain.pem +76 -0
  12. data/conf/codedeployagent.yml +9 -0
  13. data/init.d/codedeploy-agent +61 -0
  14. data/lib/core_ext.rb +71 -0
  15. data/lib/instance_agent.rb +35 -0
  16. data/lib/instance_agent/agent/base.rb +34 -0
  17. data/lib/instance_agent/codedeploy_plugin/application_specification/ace_info.rb +133 -0
  18. data/lib/instance_agent/codedeploy_plugin/application_specification/acl_info.rb +163 -0
  19. data/lib/instance_agent/codedeploy_plugin/application_specification/application_specification.rb +142 -0
  20. data/lib/instance_agent/codedeploy_plugin/application_specification/context_info.rb +23 -0
  21. data/lib/instance_agent/codedeploy_plugin/application_specification/file_info.rb +23 -0
  22. data/lib/instance_agent/codedeploy_plugin/application_specification/linux_permission_info.rb +121 -0
  23. data/lib/instance_agent/codedeploy_plugin/application_specification/mode_info.rb +66 -0
  24. data/lib/instance_agent/codedeploy_plugin/application_specification/range_info.rb +134 -0
  25. data/lib/instance_agent/codedeploy_plugin/application_specification/script_info.rb +27 -0
  26. data/lib/instance_agent/codedeploy_plugin/codedeploy_control.rb +72 -0
  27. data/lib/instance_agent/codedeploy_plugin/command_executor.rb +357 -0
  28. data/lib/instance_agent/codedeploy_plugin/command_poller.rb +146 -0
  29. data/lib/instance_agent/codedeploy_plugin/deployment_specification.rb +150 -0
  30. data/lib/instance_agent/codedeploy_plugin/hook_executor.rb +206 -0
  31. data/lib/instance_agent/codedeploy_plugin/install_instruction.rb +374 -0
  32. data/lib/instance_agent/codedeploy_plugin/installer.rb +143 -0
  33. data/lib/instance_agent/codedeploy_plugin/request_helper.rb +28 -0
  34. data/lib/instance_agent/config.rb +43 -0
  35. data/lib/instance_agent/log.rb +3 -0
  36. data/lib/instance_agent/platform.rb +17 -0
  37. data/lib/instance_agent/platform/linux_util.rb +57 -0
  38. data/lib/instance_agent/runner/child.rb +57 -0
  39. data/lib/instance_agent/runner/master.rb +103 -0
  40. data/lib/instance_metadata.rb +47 -0
  41. data/test/certificate_helper.rb +120 -0
  42. data/test/helpers/instance_agent_helper.rb +25 -0
  43. data/test/instance_agent/agent/base_test.rb +49 -0
  44. data/test/instance_agent/codedeploy_plugin/application_specification_test.rb +1710 -0
  45. data/test/instance_agent/codedeploy_plugin/codedeploy_control_test.rb +51 -0
  46. data/test/instance_agent/codedeploy_plugin/command_executor_test.rb +513 -0
  47. data/test/instance_agent/codedeploy_plugin/command_poller_test.rb +459 -0
  48. data/test/instance_agent/codedeploy_plugin/deployment_specification_test.rb +335 -0
  49. data/test/instance_agent/codedeploy_plugin/hook_executor_test.rb +250 -0
  50. data/test/instance_agent/codedeploy_plugin/install_instruction_test.rb +566 -0
  51. data/test/instance_agent/codedeploy_plugin/installer_test.rb +519 -0
  52. data/test/instance_agent/codedeploy_plugin/request_helper_test.rb +37 -0
  53. data/test/instance_agent/config_test.rb +64 -0
  54. data/test/instance_agent/runner/child_test.rb +87 -0
  55. data/test/instance_metadata_test.rb +97 -0
  56. data/test/test_helper.rb +16 -0
  57. data/vendor/gems/.codedeploy-commands-1.0.0.created.rid +1 -0
  58. data/vendor/gems/codedeploy-commands/apis/CodeDeployCommand.api.json +372 -0
  59. data/vendor/gems/codedeploy-commands/codedeploy-commands-1.0.0.gemspec +28 -0
  60. data/vendor/gems/codedeploy-commands/lib/aws/codedeploy_commands.rb +18 -0
  61. data/vendor/gems/codedeploy-commands/lib/aws/plugins/certificate_authority.rb +12 -0
  62. data/vendor/gems/codedeploy-commands/lib/aws/plugins/deploy_control_endpoint.rb +22 -0
  63. data/vendor/gems/process_manager/README.md +1 -0
  64. data/vendor/gems/process_manager/lib/blank.rb +153 -0
  65. data/vendor/gems/process_manager/lib/core_ext.rb +73 -0
  66. data/vendor/gems/process_manager/lib/process_manager.rb +49 -0
  67. data/vendor/gems/process_manager/lib/process_manager/child.rb +119 -0
  68. data/vendor/gems/process_manager/lib/process_manager/config.rb +112 -0
  69. data/vendor/gems/process_manager/lib/process_manager/log.rb +107 -0
  70. data/vendor/gems/process_manager/lib/process_manager/master.rb +322 -0
  71. data/vendor/gems/process_manager/process_manager-0.0.13.gemspec +42 -0
  72. data/vendor/specifications/aws-sdk-core-2.0.5.gemspec +39 -0
  73. data/vendor/specifications/builder-3.2.2.gemspec +29 -0
  74. data/vendor/specifications/codedeploy-commands-1.0.0.gemspec +28 -0
  75. data/vendor/specifications/gli-2.5.6.gemspec +51 -0
  76. data/vendor/specifications/jamespath-0.5.1.gemspec +35 -0
  77. data/vendor/specifications/little-plugger-1.1.3.gemspec +32 -0
  78. data/vendor/specifications/logging-1.8.1.gemspec +44 -0
  79. data/vendor/specifications/multi_json-1.7.7.gemspec +30 -0
  80. data/vendor/specifications/multi_json-1.8.4.gemspec +30 -0
  81. data/vendor/specifications/multi_xml-0.5.5.gemspec +30 -0
  82. data/vendor/specifications/process_manager-0.0.13.gemspec +42 -0
  83. data/vendor/specifications/simple_pid-0.2.1.gemspec +28 -0
  84. metadata +377 -0
@@ -0,0 +1,374 @@
1
+ require 'etc'
2
+ require 'fileutils'
3
+ require 'json'
4
+
5
+ module InstanceAgent
6
+ module CodeDeployPlugin
7
+ class InstallInstruction
8
+ def self.generate_commands_from_file(file)
9
+ name = File.basename(file.path)
10
+ file = File.open(file.path, 'r')
11
+ contents = file.read
12
+ file.close
13
+ if name =~ /^*-install.json/
14
+ parse_install_commands(contents)
15
+ elsif name =~ /^*-cleanup/
16
+ parse_remove_commands(contents)
17
+ end
18
+ end
19
+
20
+ def self.parse_install_commands(contents)
21
+ instructions = JSON.parse(contents)['instructions']
22
+ commands = []
23
+ instructions.each do |mapping|
24
+ case mapping['type']
25
+ when "copy"
26
+ commands << CopyCommand.new(mapping["source"], mapping["destination"])
27
+ when "mkdir"
28
+ commands << MakeDirectoryCommand.new(mapping["directory"])
29
+ when "chmod"
30
+ commands << ChangeModeCommand.new(mapping['file'], mapping['mode'])
31
+ when "chown"
32
+ commands << ChangeOwnerCommand.new(mapping['file'], mapping['owner'], mapping['group'])
33
+ when "setfacl"
34
+ commands << ChangeAclCommand.new(mapping['file'], InstanceAgent::CodeDeployPlugin::ApplicationSpecification::AclInfo.new(mapping['acl']))
35
+ when "semanage"
36
+ if !mapping['context']['role'].nil?
37
+ raise "Attempt to set role on object, not supported"
38
+ end
39
+ commands << ChangeContextCommand.new(mapping['file'], InstanceAgent::CodeDeployPlugin::ApplicationSpecification::ContextInfo.new(mapping['context']))
40
+ else
41
+ raise "Unknown command: #{mapping}"
42
+ end
43
+ end
44
+ commands
45
+ end
46
+
47
+ def self.parse_remove_commands(contents)
48
+ return [] if contents.empty?
49
+ #remove the unfinished paths
50
+ lines = contents.lines.to_a
51
+ if lines.last[lines.last.length-1] != "\n"
52
+ lines.pop
53
+ end
54
+ commands = []
55
+ lines.each do |command|
56
+ if command.start_with?("semanage\0")
57
+ commands << RemoveContextCommand.new(command.split("\0",2)[1].strip)
58
+ else
59
+ commands << RemoveCommand.new(command.strip)
60
+ end
61
+ end
62
+ commands.reverse
63
+ end
64
+
65
+ def self.generate_instructions()
66
+ command_builder = CommandBuilder.new()
67
+ yield(command_builder)
68
+ command_builder
69
+ end
70
+ end
71
+
72
+ class CommandBuilder
73
+ attr_reader :command_array
74
+ def initialize()
75
+ @command_array = []
76
+ @copy_targets = Hash.new
77
+ @mkdir_targets = Set.new
78
+ @permission_targets = Set.new
79
+ end
80
+
81
+ def copy(source, destination)
82
+ destination = sanitize_dir_path(destination)
83
+ log(:debug, "Copying #{source} to #{destination}")
84
+ raise "Duplicate copy instruction to #{destination} from #{source} and #{@copy_targets[destination]}" if @copy_targets.has_key?(destination)
85
+ raise "Duplicate copy instruction to #{destination} from #{source} which is already being installed as a directory" if @mkdir_targets.include?(destination)
86
+ @command_array << CopyCommand.new(source, destination)
87
+ @copy_targets[destination] = source
88
+ end
89
+
90
+ def mkdir(destination)
91
+ destination = sanitize_dir_path(destination)
92
+ log(:debug, "Making directory #{destination}")
93
+ raise "Duplicate mkdir instruction for #{destination} which is already being copied from #{@copy_targets[destination]}" if @copy_targets.has_key?(destination)
94
+ @command_array << MakeDirectoryCommand.new(destination) unless @mkdir_targets.include?(destination)
95
+ @mkdir_targets.add(destination)
96
+ end
97
+
98
+ def set_permissions(object, permission)
99
+ object = sanitize_dir_path(object)
100
+ log(:debug, "Setting permissions on #{object}")
101
+ raise "Duplicate permission setting instructions for #{object}" if @permission_targets.include?(object)
102
+ @permission_targets.add(object)
103
+
104
+ if !permission.mode.nil?
105
+ log(:debug, "Setting mode on #{object}")
106
+ @command_array << ChangeModeCommand.new(object, permission.mode.mode)
107
+ end
108
+
109
+ if !permission.acls.nil?
110
+ log(:debug, "Setting acl on #{object}")
111
+ @command_array << ChangeAclCommand.new(object, permission.acls)
112
+ end
113
+
114
+ if !permission.context.nil?
115
+ log(:debug, "Setting context on #{object}")
116
+ @command_array << ChangeContextCommand.new(object, permission.context)
117
+ end
118
+
119
+ if !permission.owner.nil? || !permission.group.nil?
120
+ log(:debug, "Setting ownership of #{object}")
121
+ @command_array << ChangeOwnerCommand.new(object, permission.owner, permission.group)
122
+ end
123
+ end
124
+
125
+ def copying_file?(file)
126
+ file = sanitize_dir_path(file)
127
+ log(:debug, "Checking for #{file} in #{@copy_targets.keys.inspect}")
128
+ @copy_targets.has_key?(file)
129
+ end
130
+
131
+ def making_directory?(dir)
132
+ dir = sanitize_dir_path(dir)
133
+ log(:debug, "Checking for #{dir} in #{@mkdir_targets.inspect}")
134
+ @mkdir_targets.include?(dir)
135
+ end
136
+
137
+ def find_matches(permission)
138
+ log(:debug, "Finding matches for #{permission.object}")
139
+ matches = []
140
+ if permission.type.include?("file")
141
+ @copy_targets.keys.each do |object|
142
+ log(:debug, "Checking #{object}")
143
+ if (permission.matches_pattern?(object) && !permission.matches_except?(object))
144
+ log(:debug, "Found match #{object}")
145
+ permission.validate_file_acl(object)
146
+ matches << object
147
+ end
148
+ end
149
+ end
150
+ if permission.type.include?("directory")
151
+ @mkdir_targets.each do |object|
152
+ log(:debug, "Checking #{object}")
153
+ if (permission.matches_pattern?(object) && !permission.matches_except?(object))
154
+ log(:debug, "Found match #{object}")
155
+ matches << object
156
+ end
157
+ end
158
+ end
159
+ matches
160
+ end
161
+
162
+ def to_json
163
+ command_json = @command_array.map(&:to_h)
164
+ {"instructions" => command_json}.to_json
165
+ end
166
+
167
+ def each(&block)
168
+ @command_array.each(&block)
169
+ end
170
+
171
+ private
172
+ def sanitize_dir_path(path)
173
+ File.expand_path(path)
174
+ end
175
+
176
+ private
177
+ def description
178
+ self.class.to_s
179
+ end
180
+
181
+ private
182
+ def log(severity, message)
183
+ raise ArgumentError, "Unknown severity #{severity.inspect}" unless InstanceAgent::Log::SEVERITIES.include?(severity.to_s)
184
+ InstanceAgent::Log.send(severity.to_sym, "#{description}: #{message}")
185
+ end
186
+ end
187
+
188
+ class RemoveCommand
189
+ def initialize(location)
190
+ @file_path = location
191
+ end
192
+ def execute
193
+ #If the file doesn't exist the command is ignored
194
+ if File.exist?(@file_path)
195
+ if File.directory?(@file_path)
196
+ # TODO (AWSGLUE-713): handle the exception if the directory is non-empty;
197
+ # this might mean the customer has put files in this directory and we should
198
+ # probably ignore the error and move on
199
+ FileUtils.rmdir(@file_path)
200
+ else
201
+ FileUtils.rm(@file_path)
202
+ end
203
+ end
204
+ end
205
+ end
206
+
207
+ class CopyCommand
208
+ attr_reader :destination, :source
209
+ def initialize(source, destination)
210
+ @source = source
211
+ @destination = destination
212
+ end
213
+
214
+ def execute(cleanup_file)
215
+ raise "File already exists at #{@destination}" if File.exists?(@destination)
216
+ cleanup_file.puts(@destination)
217
+ if File.symlink?(@source)
218
+ FileUtils.symlink(File.readlink(@source), @destination)
219
+ else
220
+ FileUtils.copy(@source, @destination, :preserve => true)
221
+ end
222
+ end
223
+
224
+ def to_h
225
+ {"type" => "copy", "source" => @source, "destination" => @destination}
226
+ end
227
+ end
228
+
229
+ class MakeDirectoryCommand
230
+ def initialize(destination)
231
+ @directory = destination
232
+ end
233
+
234
+ def execute(cleanup_file)
235
+ raise "File already exists at #{@directory}" if
236
+ File.exists?(@directory)
237
+ FileUtils.mkdir(@directory)
238
+ cleanup_file.puts(@directory)
239
+ end
240
+
241
+ def to_h
242
+ {"type" => "mkdir", "directory" => @directory}
243
+ end
244
+ end
245
+
246
+ class ChangeModeCommand
247
+ def initialize(object, mode)
248
+ @object = object
249
+ @mode = mode
250
+ end
251
+
252
+ def execute(cleanup_file)
253
+ File.chmod(@mode.to_i(8), @object)
254
+ end
255
+
256
+ def to_h
257
+ {"type" => "chmod", "mode" => @mode, "file" => @object}
258
+ end
259
+ end
260
+
261
+ class ChangeAclCommand
262
+ def initialize(object, acl)
263
+ @object = object
264
+ @acl = acl
265
+ end
266
+
267
+ def execute(cleanup_file)
268
+ begin
269
+ get_full_acl
270
+ acl = @acl.get_acl.join(",")
271
+ if !system("setfacl --set #{acl} #{@object}")
272
+ raise "Unable to set acl correctly: setfacl --set #{acl} #{@object}, exit code: #{$?}"
273
+ end
274
+ ensure
275
+ clear_full_acl
276
+ end
277
+ end
278
+
279
+ def clear_full_acl
280
+ @acl.clear_additional
281
+ end
282
+
283
+ def get_full_acl()
284
+ perm = "%o" % File.stat(@object).mode
285
+ perm = perm[-3,3]
286
+ @acl.add_ace(":#{perm[0]}")
287
+ @acl.add_ace("g::#{perm[1]}")
288
+ @acl.add_ace("o::#{perm[2]}")
289
+ if @acl.has_base_named? && !@acl.has_base_mask?
290
+ @acl.add_ace("m::#{perm[1]}")
291
+ end
292
+ if @acl.has_default?
293
+ if !@acl.has_default_user?
294
+ @acl.add_ace("d::#{perm[0]}")
295
+ end
296
+ if !@acl.has_default_group?
297
+ @acl.add_ace("d:g::#{perm[1]}")
298
+ end
299
+ if !@acl.has_default_other?
300
+ @acl.add_ace("d:o:#{perm[2]}")
301
+ end
302
+ if @acl.has_default_named? && !@acl.has_default_mask?
303
+ @acl.add_ace(@acl.get_default_group_ace.sub("group:","mask"))
304
+ end
305
+ end
306
+ end
307
+
308
+ def to_h
309
+ {"type" => "setfacl", "acl" => @acl.get_acl, "file" => @object}
310
+ end
311
+ end
312
+
313
+ class ChangeOwnerCommand
314
+ def initialize(object, owner, group)
315
+ @object = object
316
+ @owner = owner
317
+ @group = group
318
+ end
319
+
320
+ def execute(cleanup_file)
321
+ ownerid = Etc.getpwnam(@owner).uid if @owner
322
+ groupid = Etc.getgrnam(@group).gid if @group
323
+ File.chown(ownerid, groupid, @object)
324
+ end
325
+
326
+ def to_h
327
+ {"type" => "chown", "owner" => @owner, "group" => @group, "file" => @object}
328
+ end
329
+ end
330
+
331
+ class ChangeContextCommand
332
+ def initialize(object, context)
333
+ @object = object
334
+ @context = context
335
+ end
336
+
337
+ def execute(cleanup_file)
338
+ if !@context.role.nil?
339
+ raise "Attempt to set role on object, not supported"
340
+ end
341
+ args = "-t #{@context.type}"
342
+ if (!@context.user.nil?)
343
+ args = "-s #{@context.user} " + args
344
+ end
345
+ if (!@context.range.nil?)
346
+ args = args + " -r #{@context.range.get_range}"
347
+ end
348
+
349
+ object = File.realpath(@object)
350
+ if !system("semanage fcontext -a #{args} #{object}")
351
+ raise "Unable to set context: semanage fcontext -a #{args} #{object}, exit code: #{$?}"
352
+ end
353
+ if !system("restorecon -v #{object}")
354
+ raise "Unable to apply context: restorcecon -v #{object}, exit code: #{$?}"
355
+ end
356
+ cleanup_file.puts("semanage\0#{object}")
357
+ end
358
+
359
+ def to_h
360
+ {"type" => "semanage", "context" => {"user" => @context.user, "role" => @context.role, "type" => @context.type, "range" => @context.range.get_range}, "file" => @object}
361
+ end
362
+ end
363
+
364
+ class RemoveContextCommand
365
+ def initialize(object)
366
+ @object = object
367
+ end
368
+
369
+ def execute
370
+ system("semanage fcontext -d #{@object}")
371
+ end
372
+ end
373
+ end
374
+ end
@@ -0,0 +1,143 @@
1
+ require 'instance_agent/codedeploy_plugin/install_instruction'
2
+
3
+ module InstanceAgent
4
+ module CodeDeployPlugin
5
+
6
+ # Manages install and cleanup files. Also generates and executes
7
+ # install instructions based on the files section of the
8
+ # application specification file.
9
+ class Installer
10
+
11
+ attr_reader :deployment_archive_dir
12
+ attr_reader :deployment_instructions_dir
13
+
14
+ def initialize(opts = {})
15
+ raise "the deployment_archive_dir option is required" if
16
+ opts[:deployment_archive_dir].nil?
17
+ raise "the deployment_instructions_dir option is required" if
18
+ opts[:deployment_instructions_dir].nil?
19
+
20
+ @deployment_archive_dir = opts[:deployment_archive_dir]
21
+ @deployment_instructions_dir = opts[:deployment_instructions_dir]
22
+ end
23
+
24
+ def install(deployment_group_id, application_specification)
25
+ cleanup_file = File.join(deployment_instructions_dir, "#{deployment_group_id}-cleanup")
26
+
27
+ if File.exists?(cleanup_file)
28
+ InstallInstruction.parse_remove_commands(File.read(cleanup_file)).each do |cmd|
29
+ cmd.execute
30
+ end
31
+
32
+ FileUtils.rm(cleanup_file)
33
+ end
34
+
35
+ instructions = generate_instructions(application_specification)
36
+
37
+ install_file = File.join(deployment_instructions_dir, "#{deployment_group_id}-install.json")
38
+ File.open(install_file, "w") do |f|
39
+ f.write(instructions.to_json)
40
+ end
41
+
42
+ File.open(cleanup_file, "w") do |f|
43
+ instructions.each do |cmd|
44
+ cmd.execute(f)
45
+ end
46
+ end
47
+ end
48
+
49
+ private
50
+ def generate_instructions(application_specification)
51
+ InstallInstruction.generate_instructions() do |i|
52
+ application_specification.files.each do |fi|
53
+
54
+ absolute_source_path = File.join(deployment_archive_dir,
55
+ fi.source)
56
+
57
+ log(:debug, "generating instructions for copying #{fi.source} to #{fi.destination}")
58
+ if File.directory?(absolute_source_path)
59
+ fill_in_missing_ancestors(i, fi.destination)
60
+ generate_directory_copy(i, absolute_source_path, fi.destination)
61
+ else
62
+ file_destination = File.join(fi.destination, File.basename(absolute_source_path))
63
+ fill_in_missing_ancestors(i, file_destination)
64
+ generate_normal_copy(i, absolute_source_path, file_destination)
65
+ end
66
+ end
67
+
68
+ (application_specification.permissions || []).each do |permission|
69
+ object = permission.object
70
+
71
+ log(:debug, "generating instructions for setting permissions on object #{object}")
72
+ log(:debug, "it is an existing directory - #{File.directory?(object)}")
73
+ if i.copying_file?(object)
74
+ if permission.type.include?("file")
75
+ log(:debug, "found matching file #{object} to set permissions on")
76
+ permission.validate_file_permission
77
+ permission.validate_file_acl(object)
78
+ i.set_permissions(object, permission)
79
+ end
80
+ elsif (i.making_directory?(object) || File.directory?(object))
81
+ log(:debug, "found matching directory #{object} to search for objects to set permissions on")
82
+ i.find_matches(permission).each do|match|
83
+ log(:debug, "found matching object #{match} to set permissions on")
84
+ i.set_permissions(match, permission)
85
+ end
86
+ end
87
+ end
88
+ end
89
+ end
90
+
91
+ private
92
+ def generate_directory_copy(i, absolute_source_path, destination)
93
+ unless File.directory?(destination)
94
+ i.mkdir(destination)
95
+ end
96
+
97
+ (Dir.entries(absolute_source_path) - [".", ".."]).each do |entry|
98
+ absolute_entry_path = File.join(absolute_source_path, entry)
99
+ entry_destination = File.join(destination, entry)
100
+ if File.directory?(absolute_entry_path)
101
+ generate_directory_copy(i, absolute_entry_path, entry_destination)
102
+ else
103
+ generate_normal_copy(i, absolute_entry_path, entry_destination)
104
+ end
105
+ end
106
+ end
107
+
108
+ private
109
+ def generate_normal_copy(i, absolute_source_path, destination)
110
+ raise "File already exists at location #{destination}" if
111
+ File.exists?(destination)
112
+
113
+ i.copy(absolute_source_path, destination)
114
+ end
115
+
116
+ private
117
+ def fill_in_missing_ancestors(i, destination)
118
+ missing_ancestors = []
119
+ parent_dir = File.dirname(destination)
120
+ while !File.exists?(parent_dir) &&
121
+ parent_dir != "." && parent_dir != "/"
122
+ missing_ancestors.unshift(parent_dir)
123
+ parent_dir = File.dirname(parent_dir)
124
+ end
125
+
126
+ missing_ancestors.each do |dir|
127
+ i.mkdir(dir)
128
+ end
129
+ end
130
+
131
+ private
132
+ def description
133
+ self.class.to_s
134
+ end
135
+
136
+ private
137
+ def log(severity, message)
138
+ raise ArgumentError, "Unknown severity #{severity.inspect}" unless InstanceAgent::Log::SEVERITIES.include?(severity.to_s)
139
+ InstanceAgent::Log.send(severity.to_sym, "#{description}: #{message}")
140
+ end
141
+ end
142
+ end
143
+ end