aws-codedeploy-agent 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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