boxgrinder-build 0.8.1 → 0.9.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (64) hide show
  1. data/CHANGELOG +14 -0
  2. data/README.md +136 -0
  3. data/Rakefile +11 -6
  4. data/bin/boxgrinder-build +199 -0
  5. data/boxgrinder-build.gemspec +26 -14
  6. data/lib/boxgrinder-build/appliance.rb +6 -6
  7. data/lib/boxgrinder-build/helpers/guestfs-helper.rb +5 -3
  8. data/lib/boxgrinder-build/helpers/image-helper.rb +6 -0
  9. data/lib/boxgrinder-build/helpers/plugin-helper.rb +15 -4
  10. data/lib/boxgrinder-build/plugins/base-plugin.rb +1 -0
  11. data/lib/boxgrinder-build/plugins/delivery/ebs/ebs-plugin.rb +280 -0
  12. data/lib/boxgrinder-build/plugins/delivery/local/local-plugin.rb +66 -0
  13. data/lib/boxgrinder-build/plugins/delivery/s3/s3-plugin.rb +242 -0
  14. data/lib/boxgrinder-build/plugins/delivery/s3/src/cert-ec2.pem +23 -0
  15. data/lib/boxgrinder-build/plugins/delivery/sftp/sftp-plugin.rb +152 -0
  16. data/lib/boxgrinder-build/plugins/delivery/usb/usb-plugin.rb +43 -0
  17. data/lib/boxgrinder-build/plugins/os/centos/centos-plugin.rb +46 -0
  18. data/lib/boxgrinder-build/plugins/os/fedora/fedora-plugin.rb +61 -0
  19. data/lib/boxgrinder-build/plugins/os/rhel/rhel-plugin.rb +67 -0
  20. data/lib/boxgrinder-build/plugins/os/rpm-based/kickstart.rb +118 -0
  21. data/lib/boxgrinder-build/plugins/os/rpm-based/rpm-based-os-plugin.rb +202 -0
  22. data/lib/boxgrinder-build/plugins/os/rpm-based/rpm-dependency-validator.rb +153 -0
  23. data/lib/boxgrinder-build/plugins/os/rpm-based/src/appliance.ks.erb +37 -0
  24. data/lib/boxgrinder-build/plugins/os/rpm-based/src/base.repo +4 -0
  25. data/lib/boxgrinder-build/plugins/os/rpm-based/src/motd.init +21 -0
  26. data/lib/boxgrinder-build/plugins/platform/ec2/ec2-plugin.rb +239 -0
  27. data/lib/boxgrinder-build/plugins/platform/ec2/src/fstab_32bit +7 -0
  28. data/lib/boxgrinder-build/plugins/platform/ec2/src/fstab_64bit +7 -0
  29. data/lib/boxgrinder-build/plugins/platform/ec2/src/ifcfg-eth0 +7 -0
  30. data/lib/boxgrinder-build/plugins/platform/ec2/src/menu.lst +6 -0
  31. data/lib/boxgrinder-build/plugins/platform/ec2/src/rc_local +19 -0
  32. data/lib/boxgrinder-build/plugins/platform/virtualbox/virtualbox-plugin.rb +62 -0
  33. data/lib/boxgrinder-build/plugins/platform/vmware/src/README-enterprise +18 -0
  34. data/lib/boxgrinder-build/plugins/platform/vmware/src/README-personal +16 -0
  35. data/lib/boxgrinder-build/plugins/platform/vmware/src/base.vmdk +20 -0
  36. data/lib/boxgrinder-build/plugins/platform/vmware/src/base.vmx +45 -0
  37. data/lib/boxgrinder-build/plugins/platform/vmware/vmware-plugin.rb +194 -0
  38. data/rubygem-boxgrinder-build.spec +73 -10
  39. data/spec/appliance-spec.rb +2 -2
  40. data/spec/helpers/guestfs-helper-spec.rb +4 -2
  41. data/spec/helpers/image-helper-spec.rb +4 -0
  42. data/spec/helpers/plugin-helper-spec.rb +0 -38
  43. data/spec/managers/plugin-manager-spec.rb +6 -6
  44. data/spec/plugins/delivery/ebs/ebs-plugin-spec.rb +230 -0
  45. data/spec/plugins/delivery/ebs/ebs.yaml +3 -0
  46. data/spec/plugins/delivery/local/local-plugin-spec.rb +133 -0
  47. data/spec/plugins/delivery/s3/s3-plugin-spec.rb +351 -0
  48. data/spec/plugins/delivery/sftp/sftp-plugin-spec.rb +26 -0
  49. data/spec/plugins/os/centos/centos-plugin-spec.rb +52 -0
  50. data/spec/plugins/os/fedora/fedora-plugin-spec.rb +73 -0
  51. data/spec/plugins/os/rhel/rhel-plugin-spec.rb +158 -0
  52. data/spec/plugins/os/rpm-based/kickstart-spec.rb +129 -0
  53. data/spec/plugins/os/rpm-based/rpm-based-os-plugin-spec.rb +162 -0
  54. data/spec/plugins/os/rpm-based/rpm-dependency-validator-spec.rb +50 -0
  55. data/spec/plugins/os/rpm-based/src/jeos-f13-plain.ks +20 -0
  56. data/spec/plugins/os/rpm-based/src/jeos-f13-without-version.ks +22 -0
  57. data/spec/plugins/os/rpm-based/src/jeos-f13.ks +23 -0
  58. data/spec/plugins/platform/ec2/ec2-plugin-spec.rb +339 -0
  59. data/spec/plugins/platform/virtualbox/virtualbox-plugin-spec.rb +118 -0
  60. data/spec/plugins/platform/vmware/vmware-plugin-spec.rb +299 -0
  61. metadata +149 -25
  62. data/README +0 -7
  63. data/bin/boxgrinder +0 -128
  64. data/lib/boxgrinder-build/helpers/thor-helper.rb +0 -85
@@ -0,0 +1,280 @@
1
+ #
2
+ # Copyright 2010 Red Hat, Inc.
3
+ #
4
+ # This is free software; you can redistribute it and/or modify it
5
+ # under the terms of the GNU Lesser General Public License as
6
+ # published by the Free Software Foundation; either version 3 of
7
+ # the License, or (at your option) any later version.
8
+ #
9
+ # This software is distributed in the hope that it will be useful,
10
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12
+ # Lesser General Public License for more details.
13
+ #
14
+ # You should have received a copy of the GNU Lesser General Public
15
+ # License along with this software; if not, write to the Free
16
+ # Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
17
+ # 02110-1301 USA, or see the FSF site: http://www.fsf.org.
18
+
19
+ require 'rubygems'
20
+ require 'boxgrinder-build/plugins/base-plugin'
21
+ require 'AWS'
22
+ require 'open-uri'
23
+
24
+ module BoxGrinder
25
+ class EBSPlugin < BasePlugin
26
+ KERNELS = {
27
+ 'eu-west-1' => {
28
+ 'i386' => {:aki => 'aki-4deec439'},
29
+ 'x86_64' => {:aki => 'aki-4feec43b'}
30
+ },
31
+ 'ap-southeast-1' => {
32
+ 'i386' => {:aki => 'aki-13d5aa41'},
33
+ 'x86_64' => {:aki => 'aki-11d5aa43'}
34
+ },
35
+ 'us-west-1' => {
36
+ 'i386' => {:aki => 'aki-99a0f1dc'},
37
+ 'x86_64' => {:aki => 'aki-9ba0f1de'}
38
+ },
39
+ 'us-east-1' => {
40
+ 'i386' => {:aki => 'aki-407d9529'},
41
+ 'x86_64' => {:aki => 'aki-427d952b'}
42
+ }
43
+ }
44
+
45
+ def after_init
46
+ begin
47
+ @current_avaibility_zone = open('http://169.254.169.254/latest/meta-data/placement/availability-zone').string
48
+ @region = @current_avaibility_zone.scan(/((\w+)-(\w+)-(\d+))/).flatten.first
49
+ rescue
50
+ @current_avaibility_zone = nil
51
+ @region = nil
52
+ end
53
+
54
+ set_default_config_value('availability_zone', @current_avaibility_zone)
55
+ set_default_config_value('delete_on_termination', true)
56
+
57
+ register_supported_os('fedora', ['13', '14', '15'])
58
+ register_supported_os('rhel', ['6'])
59
+ end
60
+
61
+ def execute(type = :ebs)
62
+ validate_plugin_config(['access_key', 'secret_access_key', 'account_number'], 'http://boxgrinder.org/tutorials/boxgrinder-build-plugins/#EBS_Delivery_Plugin')
63
+
64
+ raise "You try to run this plugin on invalid platform. You can run EBS delivery plugin only on EC2." unless valid_platform?
65
+ raise "You can only convert to EBS type AMI appliances converted to EC2 format. Use '-p ec2' switch. For more info about EC2 plugin see http://boxgrinder.org/tutorials/boxgrinder-build-plugins/#EC2_Platform_Plugin." unless @previous_plugin_info[:name] == :ec2
66
+ raise "You selected #{@plugin_config['availability_zone']} avaibility zone, but your instance is running in #{@current_avaibility_zone} zone. Please change avaibility zone in plugin configuration file to #{@current_avaibility_zone} (see http://boxgrinder.org/tutorials/boxgrinder-build-plugins/#EBS_Delivery_Plugin) or use another instance in #{@plugin_config['availability_zone']} zone to create your EBS AMI." if @plugin_config['availability_zone'] != @current_avaibility_zone
67
+
68
+ ebs_appliance_description = "#{@appliance_config.summary} | Appliance version #{@appliance_config.version}.#{@appliance_config.release} | #{@appliance_config.hardware.arch} architecture"
69
+
70
+ @ec2 = AWS::EC2::Base.new(:access_key_id => @plugin_config['access_key'], :secret_access_key => @plugin_config['secret_access_key'])
71
+
72
+ @log.debug "Checking if appliance is already registered..."
73
+
74
+ ami_id = already_registered?(ebs_appliance_name)
75
+
76
+ if ami_id
77
+ @log.warn "EBS AMI '#{ebs_appliance_name}' is already registered as '#{ami_id}' (region: #{@region})."
78
+ return
79
+ end
80
+
81
+ @log.info "Creating new EBS volume..."
82
+
83
+ size = 0
84
+
85
+ @appliance_config.hardware.partitions.each_value { |partition| size += partition['size'] }
86
+
87
+ # create_volume with 10GB size
88
+ volume_id = @ec2.create_volume(:size => size.to_s, :availability_zone => @plugin_config['availability_zone'])['volumeId']
89
+
90
+ @log.debug "Volume #{volume_id} created."
91
+ @log.debug "Waiting for EBS volume #{volume_id} to be available..."
92
+
93
+ # wait fo volume to be created
94
+ wait_for_volume_status('available', volume_id)
95
+
96
+ # get first free device to mount the volume
97
+ suffix = free_device_suffix
98
+
99
+ @log.trace "Got free device suffix: '#{suffix}'"
100
+ @log.trace "Reading current instance id..."
101
+
102
+ # read current instance id
103
+ instance_id = open('http://169.254.169.254/latest/meta-data/instance-id').string
104
+
105
+ @log.trace "Got: #{instance_id}"
106
+ @log.info "Attaching created volume..."
107
+
108
+ # attach the volume to current host
109
+ @ec2.attach_volume(:device => "/dev/sd#{suffix}", :volume_id => volume_id, :instance_id => instance_id)
110
+
111
+ @log.debug "Waiting for EBS volume to be attached..."
112
+
113
+ # wait for volume to be attached
114
+ wait_for_volume_status('in-use', volume_id)
115
+
116
+ sleep 5 # let's wait to discover the attached volume by OS
117
+
118
+ @log.info "Copying data to EBS volume..."
119
+
120
+ ec2_disk_mount_dir = "#{@dir.tmp}/ec2-#{rand(9999999999).to_s.center(10, rand(9).to_s)}"
121
+ ebs_disk_mount_dir = "#{@dir.tmp}/ebs-#{rand(9999999999).to_s.center(10, rand(9).to_s)}"
122
+
123
+ FileUtils.mkdir_p(ec2_disk_mount_dir)
124
+ FileUtils.mkdir_p(ebs_disk_mount_dir)
125
+
126
+ begin
127
+ ec2_mounts = @image_helper.mount_image(@previous_deliverables.disk, ec2_disk_mount_dir)
128
+ rescue => e
129
+ @log.debug e
130
+ raise "Error while mounting image. See logs for more info"
131
+ end
132
+
133
+ @log.debug "Creating filesystem on volume..."
134
+
135
+ @image_helper.create_filesystem(device_for_suffix(suffix))
136
+ @exec_helper.execute("mount #{device_for_suffix(suffix)} #{ebs_disk_mount_dir}")
137
+
138
+ @log.debug "Syncing files..."
139
+
140
+ @image_helper.sync_files(ec2_disk_mount_dir, ebs_disk_mount_dir)
141
+
142
+ @log.debug "Adjusting /etc/fstab..."
143
+
144
+ adjust_fstab(ebs_disk_mount_dir)
145
+
146
+ @exec_helper.execute("umount #{ebs_disk_mount_dir}")
147
+ @image_helper.umount_image(@previous_deliverables.disk, ec2_disk_mount_dir, ec2_mounts)
148
+
149
+ FileUtils.rm_rf(ebs_disk_mount_dir)
150
+ FileUtils.rm_rf(ec2_disk_mount_dir)
151
+
152
+ @log.debug "Detaching EBS volume..."
153
+
154
+ @ec2.detach_volume(:device => "/dev/sd#{suffix}", :volume_id => volume_id, :instance_id => instance_id)
155
+
156
+ @log.debug "Waiting for EBS volume to be available..."
157
+
158
+ wait_for_volume_status('available', volume_id)
159
+
160
+ @log.info "Creating snapshot from EBS volume..."
161
+
162
+ snapshot_id = @ec2.create_snapshot(
163
+ :volume_id => volume_id,
164
+ :description => ebs_appliance_description)['snapshotId']
165
+
166
+ @log.debug "Waiting for snapshot #{snapshot_id} to be completed..."
167
+
168
+ wait_for_snapshot_status('completed', snapshot_id)
169
+
170
+ @log.debug "Deleting temporary EBS volume..."
171
+
172
+ @ec2.delete_volume(:volume_id => volume_id)
173
+
174
+ @log.info "Registering image..."
175
+
176
+ image_id = @ec2.register_image(
177
+ :block_device_mapping => [{
178
+ :device_name => '/dev/sda1',
179
+ :ebs_snapshot_id => snapshot_id,
180
+ :ebs_delete_on_termination => @plugin_config['delete_on_termination']
181
+ },
182
+ {
183
+ :device_name => '/dev/sdb',
184
+ :virtual_name => 'ephemeral0'
185
+ },
186
+ {
187
+ :device_name => '/dev/sdc',
188
+ :virtual_name => 'ephemeral1'
189
+ },
190
+ {
191
+ :device_name => '/dev/sdd',
192
+ :virtual_name => 'ephemeral2'
193
+ },
194
+ {
195
+ :device_name => '/dev/sde',
196
+ :virtual_name => 'ephemeral3'
197
+ }],
198
+ :root_device_name => '/dev/sda1',
199
+ :architecture => @appliance_config.hardware.base_arch,
200
+ :kernel_id => KERNELS[@region][@appliance_config.hardware.base_arch][:aki],
201
+ :name => ebs_appliance_name,
202
+ :description => ebs_appliance_description)['imageId']
203
+
204
+ @log.info "EBS AMI '#{ebs_appliance_name}' registered: #{image_id} (region: #{@region})"
205
+ end
206
+
207
+ def ebs_appliance_name
208
+ base_path = "#{@appliance_config.name}/#{@appliance_config.os.name}/#{@appliance_config.os.version}/#{@appliance_config.version}.#{@appliance_config.release}"
209
+
210
+ return "#{base_path}/#{@appliance_config.hardware.arch}" unless @plugin_config['snapshot']
211
+
212
+ snapshot = 1
213
+
214
+ while already_registered?("#{base_path}-SNAPSHOT-#{snapshot}/#{@appliance_config.hardware.arch}")
215
+ snapshot += 1
216
+ end
217
+
218
+ "#{base_path}-SNAPSHOT-#{snapshot}/#{@appliance_config.hardware.arch}"
219
+ end
220
+
221
+ def already_registered?(name)
222
+ images = @ec2.describe_images(:owner_id => @plugin_config['account_number'].to_s.gsub(/-/, ''))
223
+
224
+ return false if images.nil? or images['imagesSet'].nil?
225
+
226
+ images['imagesSet']['item'].each { |image| return image['imageId'] if image['name'] == name }
227
+
228
+ false
229
+ end
230
+
231
+ def adjust_fstab(ebs_mount_dir)
232
+ @exec_helper.execute("cat #{ebs_mount_dir}/etc/fstab | grep -v '/mnt' | grep -v '/data' | grep -v 'swap' > #{ebs_mount_dir}/etc/fstab.new")
233
+ @exec_helper.execute("mv #{ebs_mount_dir}/etc/fstab.new #{ebs_mount_dir}/etc/fstab")
234
+ end
235
+
236
+ def wait_for_snapshot_status(status, snapshot_id)
237
+ snapshot = @ec2.describe_snapshots(:snapshot_id => snapshot_id)['snapshotSet']['item'].first
238
+
239
+ unless snapshot['status'] == status
240
+ sleep 2
241
+ wait_for_snapshot_status(status, snapshot_id)
242
+ end
243
+ end
244
+
245
+ def wait_for_volume_status(status, volume_id)
246
+ volume = @ec2.describe_volumes(:volume_id => volume_id)['volumeSet']['item'].first
247
+
248
+ unless volume['status'] == status
249
+ sleep 2
250
+ wait_for_volume_status(status, volume_id)
251
+ end
252
+ end
253
+
254
+ def device_for_suffix(suffix)
255
+ return "/dev/sd#{suffix}" if File.exists?("/dev/sd#{suffix}")
256
+ return "/dev/xvd#{suffix}" if File.exists?("/dev/xvd#{suffix}")
257
+
258
+ raise "Not found device for suffix #{suffix}"
259
+ end
260
+
261
+ def free_device_suffix
262
+ ("f".."p").each do |suffix|
263
+ return suffix unless File.exists?("/dev/sd#{suffix}") or File.exists?("/dev/xvd#{suffix}")
264
+ end
265
+
266
+ raise "Found too many attached devices. Cannot attach EBS volume."
267
+ end
268
+
269
+ def valid_platform?
270
+ begin
271
+ open("http://169.254.169.254/1.0/meta-data/local-ipv4")
272
+ true
273
+ rescue
274
+ false
275
+ end
276
+ end
277
+ end
278
+ end
279
+
280
+ plugin :class => BoxGrinder::EBSPlugin, :type => :delivery, :name => :ebs, :full_name => "Elastic Block Storage"
@@ -0,0 +1,66 @@
1
+ #
2
+ # Copyright 2010 Red Hat, Inc.
3
+ #
4
+ # This is free software; you can redistribute it and/or modify it
5
+ # under the terms of the GNU Lesser General Public License as
6
+ # published by the Free Software Foundation; either version 3 of
7
+ # the License, or (at your option) any later version.
8
+ #
9
+ # This software is distributed in the hope that it will be useful,
10
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12
+ # Lesser General Public License for more details.
13
+ #
14
+ # You should have received a copy of the GNU Lesser General Public
15
+ # License along with this software; if not, write to the Free
16
+ # Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
17
+ # 02110-1301 USA, or see the FSF site: http://www.fsf.org.
18
+
19
+ require 'rubygems'
20
+ require 'boxgrinder-build/helpers/package-helper'
21
+ require 'boxgrinder-build/plugins/base-plugin'
22
+
23
+ module BoxGrinder
24
+ class LocalPlugin < BasePlugin
25
+ def after_init
26
+ set_default_config_value('overwrite', false)
27
+ set_default_config_value('package', true)
28
+
29
+ if @plugin_config['package']
30
+ register_deliverable(:package => "#{@appliance_config.name}-#{@appliance_config.version}.#{@appliance_config.release}-#{@appliance_config.os.name}-#{@appliance_config.os.version}-#{@appliance_config.hardware.arch}-#{current_platform}.tgz")
31
+ end
32
+ end
33
+
34
+ def execute(type = :local)
35
+ validate_plugin_config(['path'], 'http://boxgrinder.org/tutorials/boxgrinder-build-plugins/#Local_delivery_plugin')
36
+
37
+ if @plugin_config['overwrite'] or !deliverables_exists?
38
+ PackageHelper.new(@config, @appliance_config, :log => @log, :exec_helper => @exec_helper).package(File.dirname(@previous_deliverables[:disk]), @deliverables[:package]) if @plugin_config['package']
39
+
40
+ FileUtils.mkdir_p @plugin_config['path']
41
+
42
+ @log.debug "Copying files to '#{@plugin_config['path']}'..."
43
+
44
+ (@plugin_config['package'] ? @deliverables : @previous_deliverables).each_value do |file|
45
+ @log.debug "Copying '#{file}'..."
46
+ @exec_helper.execute("cp '#{file}' '#{@plugin_config['path']}'")
47
+ end
48
+ @log.info "Appliance delivered to '#{@plugin_config['path']}'."
49
+ else
50
+ @log.info "Appliance already delivered to '#{@plugin_config['path']}'."
51
+ end
52
+ end
53
+
54
+ def deliverables_exists?
55
+ (@plugin_config['package'] ? @deliverables : @previous_deliverables).each_value do |file|
56
+ return false unless File.exists?("#{@plugin_config['path']}/#{File.basename(file)}")
57
+ end
58
+
59
+ @move_deliverables = false
60
+
61
+ true
62
+ end
63
+ end
64
+ end
65
+
66
+ plugin :class => BoxGrinder::LocalPlugin, :type => :delivery, :name => :local, :full_name => "Local file system"
@@ -0,0 +1,242 @@
1
+ #
2
+ # Copyright 2010 Red Hat, Inc.
3
+ #
4
+ # This is free software; you can redistribute it and/or modify it
5
+ # under the terms of the GNU Lesser General Public License as
6
+ # published by the Free Software Foundation; either version 3 of
7
+ # the License, or (at your option) any later version.
8
+ #
9
+ # This software is distributed in the hope that it will be useful,
10
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12
+ # Lesser General Public License for more details.
13
+ #
14
+ # You should have received a copy of the GNU Lesser General Public
15
+ # License along with this software; if not, write to the Free
16
+ # Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
17
+ # 02110-1301 USA, or see the FSF site: http://www.fsf.org.
18
+
19
+ require 'rubygems'
20
+ require 'boxgrinder-build/plugins/base-plugin'
21
+ require 'boxgrinder-build/helpers/package-helper'
22
+ require 'AWS'
23
+ require 'aws'
24
+
25
+ # TODO remove this when it'll become not necessary
26
+ # quick fix for old active_support require issue in EPEL 5
27
+ require 'active_support/basic_object'
28
+ require 'active_support/duration'
29
+
30
+ module BoxGrinder
31
+ class S3Plugin < BasePlugin
32
+ REGION_OPTIONS = {
33
+ 'eu-west-1' => {
34
+ :endpoint => 's3.amazonaws.com',
35
+ :location => 'EU',
36
+ :kernel => {
37
+ 'i386' => {:aki => 'aki-4deec439'},
38
+ 'x86_64' => {:aki => 'aki-4feec43b'}
39
+ }
40
+ },
41
+
42
+ 'ap-southeast-1' => {
43
+ :endpoint => 's3-ap-southeast-1.amazonaws.com',
44
+ :location => 'ap-southeast-1',
45
+ :kernel => {
46
+ 'i386' => {:aki => 'aki-13d5aa41'},
47
+ 'x86_64' => {:aki => 'aki-11d5aa43'}
48
+ }
49
+ },
50
+
51
+ 'us-west-1' => {
52
+ :endpoint => 's3-us-west-1.amazonaws.com',
53
+ :location => 'us-west-1',
54
+ :kernel => {
55
+ 'i386' => {:aki => 'aki-99a0f1dc'},
56
+ 'x86_64' => {:aki => 'aki-9ba0f1de'}
57
+ }
58
+ },
59
+
60
+ 'us-east-1' => {
61
+ :endpoint => 's3.amazonaws.com',
62
+ :location => '',
63
+ :kernel => {
64
+ 'i386' => {:aki => 'aki-407d9529'},
65
+ 'x86_64' => {:aki => 'aki-427d952b'}
66
+ }
67
+ }
68
+ }
69
+
70
+ def after_init
71
+ set_default_config_value('overwrite', false)
72
+ set_default_config_value('path', '/')
73
+ set_default_config_value('region', 'us-east-1')
74
+
75
+ register_supported_os("fedora", ['13', '14', '15'])
76
+ register_supported_os("centos", ['5'])
77
+ register_supported_os("rhel", ['5', '6'])
78
+
79
+ @ami_build_dir = "#{@dir.base}/ami"
80
+ @ami_manifest = "#{@ami_build_dir}/#{@appliance_config.name}.ec2.manifest.xml"
81
+ end
82
+
83
+ def execute(type = :ami)
84
+ validate_plugin_config(['bucket', 'access_key', 'secret_access_key'], 'http://boxgrinder.org/tutorials/boxgrinder-build-plugins/#S3_Delivery_Plugin')
85
+
86
+ case type
87
+ when :s3
88
+ upload_to_bucket(@previous_deliverables)
89
+ when :cloudfront
90
+ upload_to_bucket(@previous_deliverables, 'public-read')
91
+ when :ami
92
+ set_default_config_value('snapshot', false)
93
+ validate_plugin_config(['cert_file', 'key_file', 'account_number'], 'http://boxgrinder.org/tutorials/boxgrinder-build-plugins/#S3_Delivery_Plugin')
94
+
95
+ @plugin_config['account_number'] = @plugin_config['account_number'].to_s.gsub(/-/, '')
96
+
97
+ @ec2 = AWS::EC2::Base.new(:access_key_id => @plugin_config['access_key'], :secret_access_key => @plugin_config['secret_access_key'], :server => "ec2.#{@plugin_config['region']}.amazonaws.com")
98
+
99
+ ami_dir = ami_key(@appliance_config.name, @plugin_config['path'])
100
+ ami_manifest_key = "#{ami_dir}/#{@appliance_config.name}.ec2.manifest.xml"
101
+
102
+ if !s3_object_exists?(ami_manifest_key) or @plugin_config['snapshot']
103
+ bundle_image(@previous_deliverables)
104
+ fix_sha1_sum
105
+ upload_image(ami_dir)
106
+ else
107
+ @log.debug "AMI for #{@appliance_config.name} appliance already uploaded, skipping..."
108
+ end
109
+
110
+ register_image(ami_manifest_key)
111
+ end
112
+ end
113
+
114
+ # https://jira.jboss.org/browse/BGBUILD-34
115
+ def fix_sha1_sum
116
+ ami_manifest = File.open(@ami_manifest).read
117
+ ami_manifest.gsub!('(stdin)= ', '')
118
+
119
+ File.open(@ami_manifest, "w") { |f| f.write(ami_manifest) }
120
+ end
121
+
122
+ def upload_to_bucket(previous_deliverables, permissions = 'private')
123
+ register_deliverable(
124
+ :package => "#{@appliance_config.name}-#{@appliance_config.version}.#{@appliance_config.release}-#{@appliance_config.os.name}-#{@appliance_config.os.version}-#{@appliance_config.hardware.arch}-#{current_platform}.tgz"
125
+ )
126
+
127
+ # quick and dirty workaround to use @deliverables[:package] later in code
128
+ FileUtils.mv(@target_deliverables[:package], @deliverables[:package]) if File.exists?(@target_deliverables[:package])
129
+
130
+ PackageHelper.new(@config, @appliance_config, :log => @log, :exec_helper => @exec_helper).package(File.dirname(previous_deliverables[:disk]), @deliverables[:package])
131
+
132
+ remote_path = "#{s3_path(@plugin_config['path'])}#{File.basename(@deliverables[:package])}"
133
+ size_b = File.size(@deliverables[:package])
134
+
135
+ key = bucket(true, permissions).key(remote_path.gsub(/^\//, '').gsub(/\/\//, ''))
136
+
137
+ unless key.exists? or @plugin_config['overwrite']
138
+ @log.info "Uploading #{File.basename(@deliverables[:package])} (#{size_b/1024/1024}MB) to '#{@plugin_config['bucket']}#{remote_path}' path..."
139
+ key.put(open(@deliverables[:package]), permissions, :server => REGION_OPTIONS[@plugin_config['region']][:endpoint])
140
+ @log.info "Appliance #{@appliance_config.name} uploaded to S3."
141
+ else
142
+ @log.info "File '#{@plugin_config['bucket']}#{remote_path}' already uploaded, skipping."
143
+ end
144
+
145
+ @s3.close_connection
146
+ end
147
+
148
+ def bucket(create_if_missing = true, permissions = 'private')
149
+ @s3 ||= Aws::S3.new(@plugin_config['access_key'], @plugin_config['secret_access_key'], :connection_mode => :single, :logger => @log, :server => REGION_OPTIONS[@plugin_config['region']][:endpoint])
150
+ @s3.bucket(@plugin_config['bucket'], create_if_missing, permissions, :location => REGION_OPTIONS[@plugin_config['region']][:location])
151
+ end
152
+
153
+ def bundle_image(deliverables)
154
+ if @plugin_config['snapshot']
155
+ @log.debug "Removing bundled image from local disk..."
156
+ FileUtils.rm_rf(@ami_build_dir)
157
+ end
158
+
159
+ return if File.exists?(@ami_build_dir)
160
+
161
+ @log.info "Bundling AMI..."
162
+
163
+ FileUtils.mkdir_p(@ami_build_dir)
164
+
165
+ @exec_helper.execute("euca-bundle-image --ec2cert #{File.dirname(__FILE__)}/src/cert-ec2.pem -i #{deliverables[:disk]} --kernel #{REGION_OPTIONS[@plugin_config['region']][:kernel][@appliance_config.hardware.base_arch][:aki]} -c #{@plugin_config['cert_file']} -k #{@plugin_config['key_file']} -u #{@plugin_config['account_number']} -r #{@appliance_config.hardware.base_arch} -d #{@ami_build_dir}", :redacted => [@plugin_config['account_number'], @plugin_config['key_file'], @plugin_config['cert_file']])
166
+
167
+ @log.info "Bundling AMI finished."
168
+ end
169
+
170
+ def upload_image(ami_dir)
171
+ bucket # this will create the bucket if needed
172
+ @log.info "Uploading #{@appliance_config.name} AMI to bucket '#{@plugin_config['bucket']}'..."
173
+
174
+ @exec_helper.execute("euca-upload-bundle -U #{@plugin_config['url'].nil? ? "http://#{REGION_OPTIONS[@plugin_config['region']][:endpoint]}" : @plugin_config['url']} -b #{@plugin_config['bucket']}/#{ami_dir} -m #{@ami_manifest} -a #{@plugin_config['access_key']} -s #{@plugin_config['secret_access_key']}", :redacted => [@plugin_config['access_key'], @plugin_config['secret_access_key']])
175
+ end
176
+
177
+ def register_image(ami_manifest_key)
178
+ info = ami_info(ami_manifest_key)
179
+
180
+ if info
181
+ @log.info "Image for #{@appliance_config.name} is registered under id: #{info.imageId} (region: #{@plugin_config['region']})."
182
+ else
183
+ info = @ec2.register_image(:image_location => "#{@plugin_config['bucket']}/#{ami_manifest_key}")
184
+ @log.info "Image for #{@appliance_config.name} successfully registered under id: #{info.imageId} (region: #{@plugin_config['region']})."
185
+ end
186
+ end
187
+
188
+ def ami_info(ami_manifest_key)
189
+ ami_info = nil
190
+
191
+ images = @ec2.describe_images(:owner_id => @plugin_config['account_number']).imagesSet
192
+
193
+ return nil if images.nil?
194
+
195
+ for image in images.item do
196
+ ami_info = image if (image.imageLocation.eql?("#{@plugin_config['bucket']}/#{ami_manifest_key}"))
197
+ end
198
+
199
+ ami_info
200
+ end
201
+
202
+ def s3_path(path)
203
+ return '' if path == '/'
204
+
205
+ "#{path.gsub(/^(\/)*/, '').gsub(/(\/)*$/, '')}/"
206
+ end
207
+
208
+ def ami_key(appliance_name, path)
209
+ base_path = "#{s3_path(path)}#{appliance_name}/#{@appliance_config.os.name}/#{@appliance_config.os.version}/#{@appliance_config.version}.#{@appliance_config.release}"
210
+
211
+ return "#{base_path}/#{@appliance_config.hardware.arch}" unless @plugin_config['snapshot']
212
+
213
+ snapshot = 1
214
+
215
+ while s3_object_exists?("#{base_path}-SNAPSHOT-#{snapshot}/#{@appliance_config.hardware.arch}/")
216
+ snapshot += 1
217
+ end
218
+
219
+ "#{base_path}-SNAPSHOT-#{snapshot}/#{@appliance_config.hardware.arch}"
220
+ end
221
+
222
+ def s3_object_exists?(path)
223
+ @log.trace "Checking if '#{path}' path exists in #{@plugin_config['bucket']}..."
224
+
225
+ begin
226
+ b = bucket(false)
227
+ # Retrieve only one or no keys (if bucket is empty), throw an exception if bucket doesn't exists
228
+ b.keys('max-keys' => 1)
229
+
230
+ if b.key(path).exists?
231
+ @log.trace "Path exists!"
232
+ return true
233
+ end
234
+ rescue
235
+ end
236
+ @log.trace "Path doesn't exist!"
237
+ false
238
+ end
239
+ end
240
+ end
241
+
242
+ plugin :class => BoxGrinder::S3Plugin, :type => :delivery, :name => :s3, :full_name => "Amazon Simple Storage Service (Amazon S3)", :types => [:s3, :cloudfront, :ami]
@@ -0,0 +1,23 @@
1
+ -----BEGIN CERTIFICATE-----
2
+ MIIDzjCCAzegAwIBAgIJALDnZV+lpZdSMA0GCSqGSIb3DQEBBQUAMIGhMQswCQYD
3
+ VQQGEwJaQTEVMBMGA1UECBMMV2VzdGVybiBDYXBlMRIwEAYDVQQHEwlDYXBlIFRv
4
+ d24xJzAlBgNVBAoTHkFtYXpvbiBEZXZlbG9wbWVudCBDZW50cmUgKFNBKTEMMAoG
5
+ A1UECxMDQUVTMREwDwYDVQQDEwhBRVMgVGVzdDEdMBsGCSqGSIb3DQEJARYOYWVz
6
+ QGFtYXpvbi5jb20wHhcNMDUwODA5MTYwMTA5WhcNMDYwODA5MTYwMTA5WjCBoTEL
7
+ MAkGA1UEBhMCWkExFTATBgNVBAgTDFdlc3Rlcm4gQ2FwZTESMBAGA1UEBxMJQ2Fw
8
+ ZSBUb3duMScwJQYDVQQKEx5BbWF6b24gRGV2ZWxvcG1lbnQgQ2VudHJlIChTQSkx
9
+ DDAKBgNVBAsTA0FFUzERMA8GA1UEAxMIQUVTIFRlc3QxHTAbBgkqhkiG9w0BCQEW
10
+ DmFlc0BhbWF6b24uY29tMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC8v/X5
11
+ zZv8CAVfNmvBM0br/RUcf1wU8xC5d2otFQQsQKB3qiWoj3oHeOWskOlTPFVZ8N+/
12
+ hEaMjyOUkg2+g6XEagCQtFCEBzUVoMjiQIBPiWj5CWkFtlav2zt33LZ0ErTND4xl
13
+ j7FQFqbaytHU9xuQcFO2p12bdITiBs5Kwoi9bQIDAQABo4IBCjCCAQYwHQYDVR0O
14
+ BBYEFPQnsX1kDVzPtX+38ACV8RhoYcw8MIHWBgNVHSMEgc4wgcuAFPQnsX1kDVzP
15
+ tX+38ACV8RhoYcw8oYGnpIGkMIGhMQswCQYDVQQGEwJaQTEVMBMGA1UECBMMV2Vz
16
+ dGVybiBDYXBlMRIwEAYDVQQHEwlDYXBlIFRvd24xJzAlBgNVBAoTHkFtYXpvbiBE
17
+ ZXZlbG9wbWVudCBDZW50cmUgKFNBKTEMMAoGA1UECxMDQUVTMREwDwYDVQQDEwhB
18
+ RVMgVGVzdDEdMBsGCSqGSIb3DQEJARYOYWVzQGFtYXpvbi5jb22CCQCw52VfpaWX
19
+ UjAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBBQUAA4GBAJJlWll4uGlrqBzeIw7u
20
+ M3RvomlxMESwGKb9gI+ZeORlnHAyZxvd9XngIcjPuU+8uc3wc10LRQUCn45a5hFs
21
+ zaCp9BSewLCCirn6awZn2tP8JlagSbjrN9YShStt8S3S/Jj+eBoRvc7jJnmEeMkx
22
+ O0wHOzp5ZHRDK7tGULD6jCfU
23
+ -----END CERTIFICATE-----