boxgrinder-build 0.9.7 → 0.9.8

Sign up to get free protection for your applications and to get access to all the features.
Files changed (32) hide show
  1. data/CHANGELOG +8 -0
  2. data/Manifest +14 -1
  3. data/bin/boxgrinder-build +17 -5
  4. data/boxgrinder-build.gemspec +2 -2
  5. data/integ/appliances/jeos-centos5-files.appl +1 -1
  6. data/integ/appliances/jeos-f16-files.appl +1 -1
  7. data/integ/spec/files-spec.rb +1 -1
  8. data/integ/spec/jeos-spec.rb +9 -0
  9. data/lib/boxgrinder-build.rb +1 -1
  10. data/lib/boxgrinder-build/appliance.rb +29 -5
  11. data/lib/boxgrinder-build/helpers/guestfs-helper.rb +1 -1
  12. data/lib/boxgrinder-build/helpers/image-helper.rb +3 -2
  13. data/lib/boxgrinder-build/plugins/base-plugin.rb +2 -4
  14. data/lib/boxgrinder-build/plugins/delivery/ebs/ebs-plugin.rb +1 -1
  15. data/lib/boxgrinder-build/plugins/delivery/s3/s3-plugin.rb +1 -1
  16. data/lib/boxgrinder-build/plugins/os/centos/centos-plugin.rb +16 -14
  17. data/lib/boxgrinder-build/plugins/os/fedora/fedora-plugin.rb +10 -20
  18. data/lib/boxgrinder-build/plugins/os/rhel/rhel-plugin.rb +1 -1
  19. data/lib/boxgrinder-build/plugins/os/rpm-based/rpm-based-os-plugin.rb +4 -2
  20. data/lib/boxgrinder-build/plugins/os/rpm-based/src/appliance.ks.erb +3 -0
  21. data/lib/boxgrinder-build/plugins/platform/ec2/ec2-plugin.rb +2 -2
  22. data/rubygem-boxgrinder-build.spec +9 -1
  23. data/spec/appliance-spec.rb +51 -1
  24. data/spec/helpers/guestfs-helper-spec.rb +33 -19
  25. data/spec/plugins/base-plugin-spec.rb +2 -4
  26. data/spec/plugins/delivery/ebs/ebs-plugin-spec.rb +1 -1
  27. data/spec/plugins/delivery/s3/s3-plugin-spec.rb +1 -1
  28. data/spec/plugins/os/fedora/fedora-plugin-spec.rb +22 -16
  29. data/spec/plugins/os/rhel/rhel-plugin-spec.rb +17 -14
  30. data/spec/plugins/os/rpm-based/rpm-based-os-plugin-spec.rb +2 -0
  31. data/spec/plugins/platform/ec2/ec2-plugin-spec.rb +1 -1
  32. metadata +4 -4
data/CHANGELOG CHANGED
@@ -1,3 +1,11 @@
1
+ v0.9.8
2
+
3
+ * [BGBUILD-312] Only use root privileges when necessary
4
+ * [BGBUILD-267] Add CentOS 6 support
5
+ * [BGBUILD-310] BoxGrinder doesn't build appliances when Fedora 16 is the host
6
+ * [BGBUILD-157] Add Alignment options for virtual appliances
7
+ * [BGBUILD-321] For EBS AMIs use the filesystem type specified for root partition
8
+
1
9
  v0.9.7
2
10
 
3
11
  * [BGBUILD-307] Appliance with swap file fails to build if selected OS is centos
data/Manifest CHANGED
@@ -89,4 +89,17 @@ spec/plugins/delivery/ebs/ebs.yaml
89
89
  spec/plugins/delivery/elastichosts/elastichosts-plugin-spec.rb
90
90
  spec/plugins/delivery/local/local-plugin-spec.rb
91
91
  spec/plugins/delivery/s3/s3-plugin-spec.rb
92
- spec/plugins/delivery/sftp/sftp-plugin-
92
+ spec/plugins/delivery/sftp/sftp-plugin-spec.rb
93
+ spec/plugins/os/centos/centos-plugin-spec.rb
94
+ spec/plugins/os/fedora/fedora-plugin-spec.rb
95
+ spec/plugins/os/rhel/rhel-plugin-spec.rb
96
+ spec/plugins/os/rpm-based/kickstart-spec.rb
97
+ spec/plugins/os/rpm-based/rpm-based-os-plugin-spec.rb
98
+ spec/plugins/os/rpm-based/rpm-dependency-validator-spec.rb
99
+ spec/plugins/os/rpm-based/src/jeos-f13-plain.ks
100
+ spec/plugins/os/rpm-based/src/jeos-f13-without-version.ks
101
+ spec/plugins/os/rpm-based/src/jeos-f13.ks
102
+ spec/plugins/os/sl/sl-plugin-spec.rb
103
+ spec/plugins/platform/ec2/ec2-plugin-spec.rb
104
+ spec/plugins/platform/virtualbox/virtualbox-plugin-spec.rb
105
+ spec/plugins/platform/vmware/vmware-plugin-spec.rb
data/bin/boxgrinder-build CHANGED
@@ -19,16 +19,13 @@
19
19
 
20
20
  require 'optparse'
21
21
  require 'rubygems'
22
+ require 'pathname'
23
+ require 'rbconfig'
22
24
  require 'hashery/opencascade'
23
25
  require 'boxgrinder-core/models/config'
24
26
  require 'boxgrinder-core/helpers/log-helper'
25
27
  require 'boxgrinder-build/appliance'
26
28
 
27
- if Process.uid != 0
28
- puts "This program must be executed with root privileges. Try 'sudo #{File.basename($0)}'."
29
- abort
30
- end
31
-
32
29
  options = OpenCascade.new(
33
30
  :platform => :none,
34
31
  :delivery => :none,
@@ -38,6 +35,8 @@ options = OpenCascade.new(
38
35
  :additional_plugins => []
39
36
  )
40
37
 
38
+ ARGV_DUP = ARGV.clone
39
+
41
40
  def validate_hash_option(options, name, value)
42
41
  value.each do |entry|
43
42
  if entry =~ /^(\S+):(\S+)$/
@@ -152,10 +151,23 @@ EOB
152
151
  end
153
152
  end
154
153
 
154
+ def ensure_root
155
+ unless Process.uid == 0
156
+ puts("Currently running as non-root user, BoxGrinder will attempt to re-launch under 'sudo -E'")
157
+ # http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-talk/151376
158
+ ruby = File.join(Config::CONFIG["bindir"],
159
+ Config::CONFIG["RUBY_INSTALL_NAME"] + Config::CONFIG["EXEEXT"])
160
+ exec("sudo -E #{ruby} -I#{$:.join(':')} #{Pathname.new(__FILE__).realpath} #{ARGV_DUP.join(" ")}")
161
+ end
162
+ end
163
+
155
164
  opts = process_options(options)
156
165
 
157
166
  begin
158
167
  opts.parse!
168
+
169
+ ensure_root
170
+
159
171
  if ARGV.empty? or ARGV.size > 1
160
172
  puts opts
161
173
  puts
@@ -2,11 +2,11 @@
2
2
 
3
3
  Gem::Specification.new do |s|
4
4
  s.name = %q{boxgrinder-build}
5
- s.version = "0.9.7"
5
+ s.version = "0.9.8"
6
6
 
7
7
  s.required_rubygems_version = Gem::Requirement.new(">= 1.2") if s.respond_to? :required_rubygems_version=
8
8
  s.authors = ["Marek Goldmann"]
9
- s.date = %q{2011-09-10}
9
+ s.date = %q{2011-11-15}
10
10
  s.default_executable = %q{boxgrinder-build}
11
11
  s.description = %q{A tool for creating appliances from simple plain text files for various virtual environments.}
12
12
  s.email = %q{info@boxgrinder.org}
@@ -11,5 +11,5 @@ files:
11
11
  "/opt/abc":
12
12
  - "http://ftp.tpnet.pl/vol/d1/apache//couchdb/1.0.3/apache-couchdb-1.0.3.tar.gz"
13
13
  - "https://raw.github.com/boxgrinder/boxgrinder-build/master/README.md"
14
- - "ftp://ftp.fu-berlin.de/unix/www/apache/couchdb/1.1.0/apache-couchdb-1.1.0.tar.gz"
14
+ - "ftp://ftp.fu-berlin.de/unix/www/squid/archive/3.0/squid-3.0.STABLE25.tar.gz"
15
15
 
@@ -10,5 +10,5 @@ files:
10
10
  "/opt/abc":
11
11
  - "http://ftp.tpnet.pl/vol/d1/apache//couchdb/1.0.3/apache-couchdb-1.0.3.tar.gz"
12
12
  - "https://raw.github.com/boxgrinder/boxgrinder-build/master/README.md"
13
- - "ftp://ftp.fu-berlin.de/unix/www/apache/couchdb/1.1.0/apache-couchdb-1.1.0.tar.gz"
13
+ - "ftp://ftp.fu-berlin.de/unix/www/squid/archive/3.0/squid-3.0.STABLE25.tar.gz"
14
14
 
@@ -55,7 +55,7 @@ module BoxGrinder
55
55
  guestfs.exists('/opt/etc/sysconfig/network').should == 1
56
56
  guestfs.exists('/opt/abc/apache-couchdb-1.0.3.tar.gz').should == 1
57
57
  guestfs.exists('/opt/abc/README.md').should == 1
58
- guestfs.exists('/opt/abc/apache-couchdb-1.1.0.tar.gz').should == 1
58
+ guestfs.exists('/opt/abc/squid-3.0.STABLE25.tar.gz').should == 1
59
59
  end
60
60
  end
61
61
 
@@ -71,6 +71,15 @@ module BoxGrinder
71
71
  @config.merge!(:platform => :ec2, :delivery => :ami)
72
72
  @appliance = Appliance.new("#{File.dirname(__FILE__)}/../appliances/jeos-centos5.appl", @config, :log => @log).create
73
73
  end
74
+
75
+ it "should build CentOS 6 JEOS" do
76
+ @appliance = Appliance.new("#{File.dirname(__FILE__)}/../appliances/jeos-centos6.appl", @config, :log => @log).create
77
+ end
78
+
79
+ it "should build CentOS 6 JEOS and create an AMI" do
80
+ @config.merge!(:platform => :ec2, :delivery => :ami)
81
+ @appliance = Appliance.new("#{File.dirname(__FILE__)}/../appliances/jeos-centos6.appl", @config, :log => @log).create
82
+ end
74
83
  end
75
84
 
76
85
  context "platform plugin" do
@@ -16,4 +16,4 @@
16
16
  # Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
17
17
  # 02110-1301 USA, or see the FSF site: http://www.fsf.org.
18
18
 
19
- require 'boxgrinder-build/appliance'
19
+ require 'boxgrinder-build/appliance'
@@ -133,14 +133,38 @@ module BoxGrinder
133
133
  def execute_plugin(plugin, param = nil)
134
134
  if plugin.deliverables_exists?
135
135
  @log.info "Deliverables for #{plugin.plugin_info[:name]} #{plugin.plugin_info[:type]} plugin exists, skipping."
136
- return
137
- end
136
+ else
137
+ @log.debug "Executing #{plugin.plugin_info[:type]} plugin..."
138
+
139
+ param.nil? ? plugin.run : plugin.run(param)
138
140
 
139
- @log.debug "Executing #{plugin.plugin_info[:type]} plugin..."
141
+ @log.debug "#{plugin.plugin_info[:type].to_s.capitalize} plugin executed."
142
+ end
143
+ if plugin.plugin_info[:type] == :os
144
+ FileUtils.chown_R(@config.uid, @config.gid, File.join(@config.dir.root, @config.dir.build))
145
+ @log.debug "Lowering from root to user."
146
+ change_user(@config.uid, @config.gid)
147
+ end
148
+ end
140
149
 
141
- param.nil? ? plugin.run : plugin.run(param)
150
+ def change_user(u, g)
151
+ begin
152
+ if Process::Sys.respond_to?(:setresgid) && Process::Sys.respond_to?(:setresuid)
153
+ Process::Sys.setresgid(g, g, g)
154
+ Process::Sys.setresuid(u, u, u)
155
+ return
156
+ end
157
+ rescue NotImplementedError
158
+ end
142
159
 
143
- @log.debug "#{plugin.plugin_info[:type].to_s.capitalize} plugin executed."
160
+ begin
161
+ # JRuby doesn't support saved ids, use this instead.
162
+ Process.gid = g
163
+ Process.egid = g
164
+ Process.uid = u
165
+ Process.euid = u
166
+ rescue NotImplementedError
167
+ end
144
168
  end
145
169
  end
146
170
  end
@@ -269,7 +269,7 @@ module BoxGrinder
269
269
  # extended partition is always #3
270
270
  partitions.delete_at(3) if partitions.size > 4
271
271
 
272
- partitions.reject { |i| !(i =~ /^#{device}/) or (@guestfs.vfs_type(i) == 'swap' and !options[:list_swap]) }
272
+ partitions.reject { |i| !(i =~ /^#{device}/) or (@guestfs.vfs_type(i) == '') or (@guestfs.vfs_type(i) == 'swap' and !options[:list_swap]) }
273
273
  end
274
274
 
275
275
  def umount_partition(part)
@@ -82,8 +82,9 @@ module BoxGrinder
82
82
  guestfs.mkmountpoint('/out')
83
83
  guestfs.mkmountpoint('/out/in')
84
84
 
85
- # Create filesystem on EC2 disk
86
- guestfs.mkfs(@appliance_config.default_filesystem_type, out_device)
85
+ # Create filesystem on destination device
86
+ # https://issues.jboss.org/browse/BGBUILD-321
87
+ guestfs.mkfs(@appliance_config.hardware.partitions['/']['type'], out_device)
87
88
  # Set root partition label
88
89
  guestfs.set_e2label(out_device, '79d3d2d4') # This is a CRC32 from /
89
90
 
@@ -179,13 +179,11 @@ module BoxGrinder
179
179
 
180
180
  def run(param = nil)
181
181
  unless is_supported_os?
182
- @log.error "#{@plugin_info[:full_name]} plugin supports following operating systems: #{supported_oses}. Your appliance contains #{@appliance_config.os.name} #{@appliance_config.os.version} operating system which is not supported by this plugin, sorry."
183
- return
182
+ raise PluginValidationError, "#{@plugin_info[:full_name]} plugin supports following operating systems: #{supported_oses}. Your appliance contains #{@appliance_config.os.name} #{@appliance_config.os.version} operating system which is not supported by this plugin, sorry."
184
183
  end
185
184
 
186
185
  unless is_supported_platform?
187
- @log.error "#{@plugin_info[:full_name]} plugin supports following platforms: #{@supported_platforms.join(', ')}. You selected #{@previous_plugin_info[:name]} platform which is not supported by this plugin, sorry."
188
- return
186
+ raise PluginValidationError, "#{@plugin_info[:full_name]} plugin supports following platforms: #{@supported_platforms.join(', ')}. You selected #{@previous_plugin_info[:name]} platform which is not supported by this plugin, sorry."
189
187
  end
190
188
 
191
189
  FileUtils.rm_rf @dir.tmp
@@ -67,7 +67,7 @@ module BoxGrinder
67
67
  def after_init
68
68
  register_supported_os('fedora', ['13', '14', '15', '16'])
69
69
  register_supported_os('rhel', ['6'])
70
- register_supported_os('centos', ['5'])
70
+ register_supported_os('centos', ['5', '6'])
71
71
  end
72
72
 
73
73
  def execute
@@ -28,7 +28,7 @@ module BoxGrinder
28
28
 
29
29
  def after_init
30
30
  register_supported_os("fedora", ['13', '14', '15', '16'])
31
- register_supported_os("centos", ['5'])
31
+ register_supported_os("centos", ['5', '6'])
32
32
  register_supported_os("rhel", ['5', '6'])
33
33
  register_supported_os("sl", ['5', '6'])
34
34
 
@@ -21,26 +21,28 @@ require 'boxgrinder-build/plugins/os/rhel/rhel-plugin'
21
21
  module BoxGrinder
22
22
  class CentOSPlugin < RHELPlugin
23
23
 
24
- CENTOS_REPOS = {
25
- "5" => {
26
- "base" => {
27
- "mirrorlist" => "http://mirrorlist.centos.org/?release=#OS_VERSION#&arch=#BASE_ARCH#&repo=os"
28
- },
29
- "updates" => {
30
- "mirrorlist" => "http://mirrorlist.centos.org/?release=#OS_VERSION#&arch=#BASE_ARCH#&repo=updates"
31
- }
32
- }
33
- }
34
-
35
24
  def after_init
36
25
  super
37
- register_supported_os('centos', ['5'])
26
+ register_supported_os('centos', ['5', '6'])
38
27
  end
39
28
 
40
29
  def execute(appliance_definition_file)
41
- build_rhel(appliance_definition_file, CENTOS_REPOS)
30
+ repos = {}
31
+
32
+ @plugin_info[:versions].each do |version|
33
+ repos[version] = {
34
+ "base" => {
35
+ "mirrorlist" => "http://mirrorlist.centos.org/?release=#OS_VERSION#&arch=#BASE_ARCH#&repo=os"
36
+ },
37
+ "updates" => {
38
+ "mirrorlist" => "http://mirrorlist.centos.org/?release=#OS_VERSION#&arch=#BASE_ARCH#&repo=updates"
39
+ }
40
+ }
41
+ end
42
+
43
+ build_rhel(appliance_definition_file, repos)
42
44
  end
43
45
  end
44
46
  end
45
47
 
46
- plugin :class => BoxGrinder::CentOSPlugin, :type => :os, :name => :centos, :full_name => "CentOS", :versions => ["5"]
48
+ plugin :class => BoxGrinder::CentOSPlugin, :type => :os, :name => :centos, :full_name => "CentOS", :versions => ["5", "6"]
@@ -23,7 +23,7 @@ module BoxGrinder
23
23
  def after_init
24
24
  super
25
25
  register_supported_os('fedora', ["13", "14", "15", "16", "rawhide"])
26
- set_default_config_value('PAE',true)
26
+ set_default_config_value('PAE', true)
27
27
  end
28
28
 
29
29
  def execute(appliance_definition_file)
@@ -45,8 +45,6 @@ module BoxGrinder
45
45
  build_with_appliance_creator(appliance_definition_file, @repos) do |guestfs, guestfs_helper|
46
46
  if @appliance_config.os.version >= "15"
47
47
  disable_biosdevname(guestfs)
48
- # https://issues.jboss.org/browse/BGBUILD-298
49
- switch_to_grub2(guestfs, guestfs_helper) if @appliance_config.os.version >= "16"
50
48
  change_runlevel(guestfs)
51
49
  disable_netfs(guestfs)
52
50
  link_mtab(guestfs)
@@ -69,27 +67,19 @@ module BoxGrinder
69
67
  @plugin_config['PAE'] ? packages << "kernel-PAE" : packages << "kernel"
70
68
  end
71
69
 
72
- packages << "-grub2" if @appliance_config.os.version >= "16"
73
- end
74
-
75
- # Since Fedora 16 by default GRUB2 is used - we remove Legacy GRUB
76
- # and use GRUB2 instead
77
- #
78
- # https://issues.jboss.org/browse/BGBUILD-280
79
- def switch_to_grub2(guestfs, guestfs_helper)
80
- @log.debug "Switching to GRUB2..."
81
- guestfs_helper.sh("yum -y remove grub")
82
- guestfs_helper.sh("yum -y install grub2")
83
- # Disabling biosdevname in GRUB2
84
- guestfs.write("/etc/default/grub", "GRUB_CMDLINE_LINUX=\"quiet rhgb biosdevname=0\"\n") if guestfs.exists("/boot/grub2/grub.cfg") != 0
85
- # We are using only one disk, so this is save
86
- guestfs.sh("cd / && grub2-install --force #{guestfs.list_devices.first}")
87
- guestfs.sh("cd / && grub2-mkconfig -o /boot/grub2/grub.cfg")
88
- @log.debug "Using GRUB2 from now."
70
+ if @appliance_config.os.version >= "16"
71
+ packages << "grub2"
72
+ else
73
+ packages << "grub"
74
+ end
89
75
  end
90
76
 
91
77
  def disable_biosdevname(guestfs)
92
78
  @log.debug "Disabling biosdevname..."
79
+ if guestfs.exists("/boot/grub2/grub.cfg") != 0
80
+ guestfs.write("/etc/default/grub", "GRUB_CMDLINE_LINUX=\"quiet rhgb biosdevname=0\"\n")
81
+ guestfs.sh("cd / && grub2-mkconfig -o /boot/grub2/grub.cfg")
82
+ end
93
83
  guestfs.sh('sed -i "s/kernel\(.*\)/kernel\1 biosdevname=0/g" /boot/grub/grub.conf') if guestfs.exists("/boot/grub/grub.conf") != 0
94
84
  @log.debug "Biosdevname disabled."
95
85
  end
@@ -36,7 +36,7 @@ module BoxGrinder
36
36
 
37
37
  def normalize_packages(packages)
38
38
  # https://issues.jboss.org/browse/BGBUILD-89
39
- add_packages(packages, ['@core', 'curl'])
39
+ add_packages(packages, ['@core', 'curl', 'grub'])
40
40
 
41
41
  case @appliance_config.os.version
42
42
  when '5'
@@ -268,8 +268,10 @@ module BoxGrinder
268
268
  end
269
269
 
270
270
  # /boot/grub/grub.conf
271
- if grub = guestfs.read_file('/boot/grub/grub.conf').gsub!(/(\/dev\/sda.)/) { |path| "LABEL=#{guestfs.vfs_label(path.gsub('/dev/sda', device))}" }
272
- guestfs.write_file('/boot/grub/grub.conf', grub, 0)
271
+ if guestfs.exists('/boot/grub/grub.conf') != 0
272
+ if grub = guestfs.read_file('/boot/grub/grub.conf').gsub!(/(\/dev\/sda.)/) { |path| "LABEL=#{guestfs.vfs_label(path.gsub('/dev/sda', device))}" }
273
+ guestfs.write_file('/boot/grub/grub.conf', grub, 0)
274
+ end
273
275
  end
274
276
  @log.debug "Done."
275
277
  end
@@ -20,6 +20,9 @@ rootpw --iscrypted <%= appliance_config.os.password.crypt((0...8).map { 65.+(ran
20
20
  # appliance-creator doesn't support labels...
21
21
  # --label Zlib.crc32(root).to_s(16)
22
22
 
23
+ <% if appliance_config.os.name == 'fedora' and appliance_config.os.version >= '16' %>
24
+ part biosboot --size 5 --fstype biosboot --ondisk sda
25
+ <% end %>
23
26
  <% mount_points.each do |root| %>
24
27
  <% partition = appliance_config.hardware.partitions[root]%>
25
28
  part <%= root %> --size <%= (partition['size'].to_f * 1024).to_i %> --fstype <%= root.eql?('swap') ? 'swap' : partition['type'] %> <% unless partition['options'].nil? %> --fsoptions '<%= partition['options'] %>' <% end %> <% if partition['passphrase'] %> --encrypted --passphrase='<%= partition['passphrase'] %>' <% end %> --ondisk sda<% end %>
@@ -26,7 +26,7 @@ module BoxGrinder
26
26
  register_deliverable(:disk => "#{@appliance_config.name}.ec2")
27
27
 
28
28
  register_supported_os('fedora', ['13', '14', '15', '16'])
29
- register_supported_os('centos', ['5'])
29
+ register_supported_os('centos', ['5', '6'])
30
30
  register_supported_os('sl', ['5', '6'])
31
31
  register_supported_os('rhel', ['5', '6'])
32
32
  end
@@ -45,7 +45,7 @@ module BoxGrinder
45
45
  # Remove normal kernel
46
46
  guestfs.sh("yum -y remove kernel")
47
47
  # because we need to install kernel-xen package
48
- guestfs.sh("yum -y install kernel-xen")
48
+ guestfs_helper.sh("yum -y install kernel-xen", :arch => @appliance_config.hardware.arch)
49
49
  # and add require modules
50
50
  @linux_helper.recreate_kernel_image(guestfs, ['xenblk', 'xennet'])
51
51
  end
@@ -5,7 +5,7 @@
5
5
 
6
6
  Summary: A tool for creating appliances from simple plain text files
7
7
  Name: rubygem-%{gemname}
8
- Version: 0.9.7
8
+ Version: 0.9.8
9
9
  Release: 1%{?dist}
10
10
  Group: Development/Languages
11
11
  License: LGPLv3+
@@ -130,6 +130,14 @@ popd
130
130
  %{gemdir}/doc/%{gemname}-%{version}
131
131
 
132
132
  %changelog
133
+ * Fri Oct 14 2011 Marc Savy <msavy@redhat.com> - 0.9.8-1
134
+ - Upstream release: 0.9.8
135
+ - [BGBUILD-312] Only use root privileges when necessary
136
+ - [BGBUILD-267] Add CentOS 6 support
137
+ - [BGBUILD-310] BoxGrinder doesn't build appliances when Fedora 16 is the host
138
+ - [BGBUILD-157] Add Alignment options for virtual appliances
139
+ - [BGBUILD-321] For EBS AMIs use the filesystem type specified for root partition
140
+
133
141
  * Tue Sep 06 2011 Marek Goldmann <mgoldman@redhat.com> - 0.9.7-1
134
142
  - Upstream release: 0.9.7
135
143
  - [BGBUILD-307] Appliance with swap file fails to build if selected OS is centos
@@ -26,7 +26,8 @@ module BoxGrinder
26
26
  describe Appliance do
27
27
  def prepare_appliance(options = {}, definition_file = "#{File.dirname(__FILE__)}/rspec/src/appliances/jeos-f13.appl")
28
28
  @log = LogHelper.new(:level => :trace, :type => :stdout)
29
- @config = OpenCascade.new(:platform => :none, :delivery => :none, :force => false).merge(options)
29
+ @config = OpenCascade.new(:platform => :none, :delivery => :none, :force => false,
30
+ :uid => 501, :gid => 501, :dir => {:root => '/', :build => 'build'}).merge(options)
30
31
 
31
32
  @plugin_manager = mock(PluginManager)
32
33
 
@@ -327,6 +328,30 @@ module BoxGrinder
327
328
 
328
329
  @appliance.execute_plugin(plugin, :s3)
329
330
  end
331
+
332
+ context "user switching" do
333
+ before(:each) do
334
+ FileUtils.stub!(:chown_R)
335
+ @appliance.stub!(:change_user)
336
+ end
337
+
338
+ it "should switch to user from root after execution of OS plugin" do
339
+ plugin = mock('OSPlugin', :deliverables_exists? => false, :plugin_info => {:name => :test, :type => :os})
340
+ FileUtils.should_receive(:chown_R).with(501, 501, '/build')
341
+ @appliance.should_receive(:change_user).with(501, 501)
342
+ plugin.should_receive(:run).with(:test)
343
+
344
+ @appliance.execute_plugin(plugin, :test)
345
+ end
346
+
347
+ it "should switch to user from root after OS plugin even if deliverables exist" do
348
+ plugin = mock('OSPlugin', :deliverables_exists? => true, :plugin_info => {:name => :test, :type => :os})
349
+ FileUtils.should_receive(:chown_R).with(501, 501, '/build')
350
+ @appliance.should_receive(:change_user).with(501, 501)
351
+
352
+ @appliance.execute_plugin(plugin, :test)
353
+ end
354
+ end
330
355
  end
331
356
 
332
357
  context "preparations" do
@@ -350,5 +375,30 @@ module BoxGrinder
350
375
  @appliance.delivery_selected?.should == false
351
376
  end
352
377
  end
378
+
379
+ context ".change_user" do
380
+ before(:each) do
381
+ prepare_appliance
382
+ end
383
+ context "Interpreters supporting changes to real, effective and saved ids" do
384
+ it "should change the real, effective and saved uid & gid" do
385
+ Process::Sys.should_receive(:respond_to?).twice.and_return(true)
386
+ Process::Sys.should_receive(:setresuid).with(501, 501, 501)
387
+ Process::Sys.should_receive(:setresgid).with(502, 502, 502)
388
+ @appliance.change_user(501, 502)
389
+ end
390
+ end
391
+ context "Interpreters only supporting changes to real and effective ids" do
392
+ it "should change the real and effective uid & gid" do
393
+ Process::Sys.should_receive(:respond_to?).once.and_return(false)
394
+ Process.should_receive(:gid=).with(502)
395
+ Process.should_receive(:egid=).with(502)
396
+ Process.should_receive(:uid=).with(501)
397
+ Process.should_receive(:euid=).with(501)
398
+ @appliance.change_user(501, 502)
399
+ end
400
+ end
401
+ end
402
+
353
403
  end
354
404
  end
@@ -228,8 +228,8 @@ module BoxGrinder
228
228
 
229
229
  @appliance_config.stub!(:hardware).and_return(OpenCascade.new(:partitions => {'/' => nil, '/home' => nil}))
230
230
  guestfs.should_receive(:list_partitions).and_return(['/dev/vda1', '/dev/vda2'])
231
- guestfs.should_receive(:vfs_type).with('/dev/vda1').and_return('ext3')
232
- guestfs.should_receive(:vfs_type).with('/dev/vda2').and_return('ext3')
231
+ guestfs.should_receive(:vfs_type).with('/dev/vda1').twice.and_return('ext3')
232
+ guestfs.should_receive(:vfs_type).with('/dev/vda2').twice.and_return('ext3')
233
233
 
234
234
  @helper.should_receive(:mount_partition).with('/dev/vda1', '/', '')
235
235
  @helper.should_receive(:mount_partition).with('/dev/vda2', '/home', '')
@@ -238,14 +238,28 @@ module BoxGrinder
238
238
  @helper.mount_partitions('/dev/vda')
239
239
  end
240
240
 
241
+ it "should mount partitions skiping partitions with undefined filesystem type" do
242
+ guestfs = mock('Guestfs')
243
+
244
+ @appliance_config.stub!(:hardware).and_return(OpenCascade.new(:partitions => {'/' => nil}))
245
+ guestfs.should_receive(:list_partitions).and_return(['/dev/vda1', '/dev/vda2'])
246
+ guestfs.should_receive(:vfs_type).with('/dev/vda1').and_return('')
247
+ guestfs.should_receive(:vfs_type).with('/dev/vda2').twice.and_return('ext3')
248
+
249
+ @helper.should_receive(:mount_partition).with('/dev/vda2', '/', '')
250
+
251
+ @helper.instance_variable_set(:@guestfs, guestfs)
252
+ @helper.mount_partitions('/dev/vda')
253
+ end
254
+
241
255
  it "should mount two partitions from three where one is a swap partition" do
242
256
  guestfs = mock('Guestfs')
243
257
 
244
258
  @appliance_config.stub!(:hardware).and_return(OpenCascade.new(:partitions => {'/' => nil, '/home' => nil, 'swap' => nil}))
245
259
  guestfs.should_receive(:list_partitions).and_return(['/dev/vda1', '/dev/vda2', '/dev/vda3'])
246
- guestfs.should_receive(:vfs_type).with('/dev/vda1').and_return('ext3')
247
- guestfs.should_receive(:vfs_type).with('/dev/vda2').and_return('ext3')
248
- guestfs.should_receive(:vfs_type).with('/dev/vda3').and_return('swap')
260
+ guestfs.should_receive(:vfs_type).with('/dev/vda1').twice.and_return('ext3')
261
+ guestfs.should_receive(:vfs_type).with('/dev/vda2').twice.and_return('ext3')
262
+ guestfs.should_receive(:vfs_type).with('/dev/vda3').twice.and_return('swap')
249
263
 
250
264
  @helper.should_receive(:mount_partition).with('/dev/vda1', '/', '')
251
265
  @helper.should_receive(:mount_partition).with('/dev/vda2', '/home', '')
@@ -259,10 +273,10 @@ module BoxGrinder
259
273
 
260
274
  @appliance_config.stub!(:hardware).and_return(OpenCascade.new(:partitions => {'/' => nil, '/home' => nil, '/var/www' => nil, '/var/mock' => nil}))
261
275
  guestfs.should_receive(:list_partitions).and_return(['/dev/vda1', '/dev/vda2', '/dev/vda3', '/dev/vda4', '/dev/vda5'])
262
- guestfs.should_receive(:vfs_type).with('/dev/vda1').and_return('ext3')
263
- guestfs.should_receive(:vfs_type).with('/dev/vda2').and_return('ext3')
264
- guestfs.should_receive(:vfs_type).with('/dev/vda3').and_return('ext4')
265
- guestfs.should_receive(:vfs_type).with('/dev/vda5').and_return('ext4')
276
+ guestfs.should_receive(:vfs_type).with('/dev/vda1').twice.and_return('ext3')
277
+ guestfs.should_receive(:vfs_type).with('/dev/vda2').twice.and_return('ext3')
278
+ guestfs.should_receive(:vfs_type).with('/dev/vda3').twice.and_return('ext4')
279
+ guestfs.should_receive(:vfs_type).with('/dev/vda5').twice.and_return('ext4')
266
280
 
267
281
  @helper.should_receive(:mount_partition).with('/dev/vda1', '/', '')
268
282
  @helper.should_receive(:mount_partition).with('/dev/vda2', '/home', '')
@@ -362,8 +376,8 @@ module BoxGrinder
362
376
  @helper.instance_variable_set(:@guestfs, guestfs)
363
377
 
364
378
  guestfs.should_receive(:list_partitions).and_return(['/dev/vda1', '/dev/vda2'])
365
- guestfs.should_receive(:vfs_type).with('/dev/vda1').and_return('ext3')
366
- guestfs.should_receive(:vfs_type).with('/dev/vda2').and_return('ext3')
379
+ guestfs.should_receive(:vfs_type).with('/dev/vda1').twice.and_return('ext3')
380
+ guestfs.should_receive(:vfs_type).with('/dev/vda2').twice.and_return('ext3')
367
381
 
368
382
  @helper.should_receive(:umount_partition).ordered.with('/dev/vda2')
369
383
  @helper.should_receive(:umount_partition).ordered.with('/dev/vda1')
@@ -429,8 +443,8 @@ module BoxGrinder
429
443
  @helper.instance_variable_set(:@guestfs, guestfs)
430
444
 
431
445
  guestfs.should_receive(:list_partitions).and_return(['/dev/sda1', '/dev/sda2'])
432
- guestfs.should_receive(:vfs_type).with('/dev/sda1').and_return('ext3')
433
- guestfs.should_receive(:vfs_type).with('/dev/sda2').and_return('swap')
446
+ guestfs.should_receive(:vfs_type).with('/dev/sda1').twice.and_return('ext3')
447
+ guestfs.should_receive(:vfs_type).with('/dev/sda2').twice.and_return('swap')
434
448
 
435
449
  @helper.mountable_partitions('/dev/sda').should == ['/dev/sda1']
436
450
  end
@@ -440,8 +454,8 @@ module BoxGrinder
440
454
  @helper.instance_variable_set(:@guestfs, guestfs)
441
455
 
442
456
  guestfs.should_receive(:list_partitions).and_return(['/dev/sda1', '/dev/sda2'])
443
- guestfs.should_receive(:vfs_type).with('/dev/sda1').and_return('ext3')
444
- guestfs.should_receive(:vfs_type).with('/dev/sda2').and_return('swap')
457
+ guestfs.should_receive(:vfs_type).with('/dev/sda1').twice.and_return('ext3')
458
+ guestfs.should_receive(:vfs_type).with('/dev/sda2').twice.and_return('swap')
445
459
 
446
460
  @helper.mountable_partitions('/dev/sda', :list_swap => true).should == ['/dev/sda1', '/dev/sda2']
447
461
  end
@@ -451,10 +465,10 @@ module BoxGrinder
451
465
  @helper.instance_variable_set(:@guestfs, guestfs)
452
466
 
453
467
  guestfs.should_receive(:list_partitions).and_return(['/dev/sda1', '/dev/sda2', '/dev/sda3', '/dev/sda4', '/dev/sda5'])
454
- guestfs.should_receive(:vfs_type).with('/dev/sda1').and_return('ext3')
455
- guestfs.should_receive(:vfs_type).with('/dev/sda2').and_return('ext3')
456
- guestfs.should_receive(:vfs_type).with('/dev/sda3').and_return('ext3')
457
- guestfs.should_receive(:vfs_type).with('/dev/sda5').and_return('ext3')
468
+ guestfs.should_receive(:vfs_type).with('/dev/sda1').twice.and_return('ext3')
469
+ guestfs.should_receive(:vfs_type).with('/dev/sda2').twice.and_return('ext3')
470
+ guestfs.should_receive(:vfs_type).with('/dev/sda3').twice.and_return('ext3')
471
+ guestfs.should_receive(:vfs_type).with('/dev/sda5').twice.and_return('ext3')
458
472
 
459
473
  @helper.mountable_partitions('/dev/sda').should == ['/dev/sda1', '/dev/sda2', '/dev/sda3', '/dev/sda5']
460
474
  end
@@ -128,15 +128,13 @@ module BoxGrinder
128
128
  it "should fail if OS is not supported" do
129
129
  @plugin.register_supported_os('fedora', ['12', '13'])
130
130
  @appliance_config.stub!(:os).and_return(OpenCascade.new({:name => 'fedora', :version => '14'}))
131
- @log.should_receive(:error).with('Amazon Simple Storage Service (Amazon S3) plugin supports following operating systems: fedora (versions: 12, 13). Your appliance contains fedora 14 operating system which is not supported by this plugin, sorry.')
132
- @plugin.run
131
+ lambda { @plugin.run }.should raise_error(PluginValidationError, 'Amazon Simple Storage Service (Amazon S3) plugin supports following operating systems: fedora (versions: 12, 13). Your appliance contains fedora 14 operating system which is not supported by this plugin, sorry.')
133
132
  end
134
133
 
135
134
  it "should fail if platform is not supported" do
136
135
  @plugin.instance_variable_set(:@previous_plugin_info, {:type => :platform, :name => :ec2})
137
136
  @plugin.register_supported_platform('vmware')
138
- @log.should_receive(:error).with('Amazon Simple Storage Service (Amazon S3) plugin supports following platforms: vmware. You selected ec2 platform which is not supported by this plugin, sorry.')
139
- @plugin.run
137
+ lambda { @plugin.run }.should raise_error(PluginValidationError, 'Amazon Simple Storage Service (Amazon S3) plugin supports following platforms: vmware. You selected ec2 platform which is not supported by this plugin, sorry.')
140
138
  end
141
139
 
142
140
  it "should not fail if previous plugin is not a platform plugin" do
@@ -86,7 +86,7 @@ module BoxGrinder
86
86
  Set.new(supported_oses.keys).should == Set.new(['fedora', 'rhel', 'centos'])
87
87
  supported_oses['rhel'].should == ['6']
88
88
  supported_oses['fedora'].should == ['13', '14', '15', '16']
89
- supported_oses['centos'].should == ['5']
89
+ supported_oses['centos'].should == ['5', '6']
90
90
  end
91
91
 
92
92
  it "should adjust fstab" do
@@ -77,7 +77,7 @@ module BoxGrinder
77
77
 
78
78
  supportes_oses.size.should == 4
79
79
  Set.new(supportes_oses.keys).should == Set.new(['centos', 'fedora', 'rhel', 'sl'])
80
- supportes_oses['centos'].should == ['5']
80
+ supportes_oses['centos'].should == ['5', '6']
81
81
  supportes_oses['rhel'].should == ['5', '6']
82
82
  supportes_oses['sl'].should == ['5', '6']
83
83
  supportes_oses['fedora'].should == ['13', '14', '15', '16']
@@ -72,31 +72,50 @@ module BoxGrinder
72
72
  @appliance_config.should_receive(:is64bit?).and_return(false)
73
73
 
74
74
  @plugin.normalize_packages(packages)
75
- packages.should == ["abc", "def", "@core", "system-config-firewall-base", "dhclient", "kernel"]
75
+ packages.should == ["abc", "def", "@core", "system-config-firewall-base", "dhclient", "kernel", "grub"]
76
+ end
77
+
78
+ it "should normalize packages for Fedora 16" do
79
+ @appliance_config.stub!(:os).and_return(OpenCascade.new(:name => 'fedora', :version => '16'))
80
+
81
+ packages = ['abc', 'def', 'kernel']
82
+
83
+ @plugin.normalize_packages(packages)
84
+ packages.should == ["abc", "def", "@core", "system-config-firewall-base", "dhclient", "kernel", "grub2"]
76
85
  end
77
86
 
78
87
  it "should normalize packages for 64bit" do
79
88
  packages = ['abc', 'def', 'kernel']
80
89
 
81
90
  @plugin.normalize_packages(packages)
82
- packages.should == ["abc", "def", "@core", "system-config-firewall-base", "dhclient", "kernel"]
91
+ packages.should == ["abc", "def", "@core", "system-config-firewall-base", "dhclient", "kernel", "grub"]
83
92
  end
84
93
 
85
94
  it "should add packages for fedora 13" do
86
95
  packages = []
87
96
 
88
97
  @plugin.normalize_packages(packages)
89
- packages.should == ["@core", "system-config-firewall-base", "dhclient", "kernel"]
98
+ packages.should == ["@core", "system-config-firewall-base", "dhclient", "kernel", "grub"]
90
99
  end
91
100
 
92
101
  context "BGBUILD-204" do
93
102
  it "should disable bios device name hints for GRUB legacy" do
94
103
  guestfs = mock("GuestFS")
104
+ guestfs.should_receive(:exists).with("/boot/grub2/grub.cfg").and_return(0)
95
105
  guestfs.should_receive(:exists).with("/boot/grub/grub.conf").and_return(1)
96
106
  guestfs.should_receive(:sh).with("sed -i \"s/kernel\\(.*\\)/kernel\\1 biosdevname=0/g\" /boot/grub/grub.conf")
97
107
  @plugin.disable_biosdevname(guestfs)
98
108
  end
99
109
 
110
+ it "should disable bios device name hints for GRUB2" do
111
+ guestfs = mock("GuestFS")
112
+ guestfs.should_receive(:exists).with("/boot/grub2/grub.cfg").and_return(1)
113
+ guestfs.should_receive(:exists).with("/boot/grub/grub.conf").and_return(0)
114
+ guestfs.should_receive(:write).with("/etc/default/grub", "GRUB_CMDLINE_LINUX=\"quiet rhgb biosdevname=0\"\n")
115
+ guestfs.should_receive(:sh).with("cd / && grub2-mkconfig -o /boot/grub2/grub.cfg")
116
+ @plugin.disable_biosdevname(guestfs)
117
+ end
118
+
100
119
  it "should change to runlevel 3 by default" do
101
120
  guestfs = mock("GuestFS")
102
121
  guestfs.should_receive(:rm).with("/etc/systemd/system/default.target")
@@ -117,18 +136,6 @@ module BoxGrinder
117
136
  @plugin.link_mtab(guestfs)
118
137
  end
119
138
 
120
- it "should replace GRUB legacy with GRUB2" do
121
- guestfs = mock("GuestFS")
122
- guestfs_helper = mock("GuestFSHelper")
123
- guestfs_helper.should_receive(:sh).ordered.with("yum -y remove grub")
124
- guestfs_helper.should_receive(:sh).ordered.with("yum -y install grub2")
125
- guestfs.should_receive(:list_devices).and_return(['/dev/vda'])
126
- guestfs.should_receive(:sh).ordered.with("cd / && grub2-install --force /dev/vda")
127
- guestfs.should_receive(:sh).ordered.with("cd / && grub2-mkconfig -o /boot/grub2/grub.cfg")
128
- guestfs.should_receive(:exists).with("/boot/grub2/grub.cfg").and_return(1)
129
- guestfs.should_receive(:write).with("/etc/default/grub", "GRUB_CMDLINE_LINUX=\"quiet rhgb biosdevname=0\"\n")
130
- @plugin.switch_to_grub2(guestfs, guestfs_helper)
131
- end
132
139
  describe ".execute" do
133
140
  it "should make Fedora 15 or higher work" do
134
141
  @appliance_config.stub!(:os).and_return(OpenCascade.new({:name => 'fedora', :version => '15'}))
@@ -155,7 +162,6 @@ module BoxGrinder
155
162
 
156
163
  @plugin.should_receive(:normalize_packages).ordered
157
164
  @plugin.should_receive(:disable_biosdevname).ordered.with(guestfs)
158
- @plugin.should_receive(:switch_to_grub2).ordered.with(guestfs, guestfs_helper)
159
165
  @plugin.should_receive(:change_runlevel).ordered.with(guestfs)
160
166
  @plugin.should_receive(:disable_netfs).ordered.with(guestfs)
161
167
  @plugin.should_receive(:link_mtab).ordered.with(guestfs)
@@ -55,14 +55,15 @@ module BoxGrinder
55
55
 
56
56
  @plugin.normalize_packages(packages)
57
57
 
58
- packages.size.should == 7
58
+ packages.size.should == 8
59
59
  packages[0].should == '@core'
60
60
  packages[1].should == 'curl'
61
- packages[2].should == 'kernel'
62
- packages[3].should == 'system-config-securitylevel-tui'
63
- packages[4].should == 'util-linux'
64
- packages[5].should == 'setarch'
65
- packages[6].should == 'sudo'
61
+ packages[2].should == 'grub'
62
+ packages[3].should == 'kernel'
63
+ packages[4].should == 'system-config-securitylevel-tui'
64
+ packages[5].should == 'util-linux'
65
+ packages[6].should == 'setarch'
66
+ packages[7].should == 'sudo'
66
67
  end
67
68
 
68
69
  it "should not add kernel package if kernel-xen is already choose" do
@@ -72,14 +73,15 @@ module BoxGrinder
72
73
 
73
74
  @plugin.normalize_packages(packages)
74
75
 
75
- packages.size.should == 7
76
+ packages.size.should == 8
76
77
  packages[0].should == 'kernel-xen'
77
78
  packages[1].should == '@core'
78
79
  packages[2].should == 'curl'
79
- packages[3].should == 'system-config-securitylevel-tui'
80
- packages[4].should == 'util-linux'
81
- packages[5].should == 'setarch'
82
- packages[6].should == 'sudo'
80
+ packages[3].should == 'grub'
81
+ packages[4].should == 'system-config-securitylevel-tui'
82
+ packages[5].should == 'util-linux'
83
+ packages[6].should == 'setarch'
84
+ packages[7].should == 'sudo'
83
85
  end
84
86
 
85
87
  it "should not add default packages for RHEL 6" do
@@ -87,11 +89,12 @@ module BoxGrinder
87
89
 
88
90
  @plugin.normalize_packages(packages)
89
91
 
90
- packages.size.should == 4
92
+ packages.size.should == 5
91
93
  packages[0].should == '@core'
92
94
  packages[1].should == 'curl'
93
- packages[2].should == 'kernel'
94
- packages[3].should == 'system-config-firewall-base'
95
+ packages[2].should == 'grub'
96
+ packages[3].should == 'kernel'
97
+ packages[4].should == 'system-config-firewall-base'
95
98
  end
96
99
  end
97
100
 
@@ -161,6 +161,7 @@ module BoxGrinder
161
161
  guestfs = mock("guestfs")
162
162
 
163
163
  guestfs.should_receive(:list_devices).and_return(['/dev/hda'])
164
+ guestfs.should_receive(:exists).with('/boot/grub/grub.conf').and_return(1)
164
165
 
165
166
  guestfs.should_receive(:read_file).with('/etc/fstab').and_return("/dev/sda1 / something\nLABEL=/boot /boot something\n")
166
167
  guestfs.should_receive(:vfs_label).with('/dev/hda1').and_return('/')
@@ -177,6 +178,7 @@ module BoxGrinder
177
178
  guestfs = mock("guestfs")
178
179
 
179
180
  guestfs.should_receive(:list_devices).and_return(['/dev/sda'])
181
+ guestfs.should_receive(:exists).with('/boot/grub/grub.conf').and_return(1)
180
182
 
181
183
  guestfs.should_receive(:read_file).with('/etc/fstab').and_return("LABEL=/ / something\nLABEL=/boot /boot something\n")
182
184
  guestfs.should_not_receive(:vfs_label)
@@ -272,7 +272,7 @@ module BoxGrinder
272
272
  guestfs.should_receive(:upload).with("/etc/resolv.conf", "/etc/resolv.conf")
273
273
  guestfs.should_receive(:mkdir).with("/data")
274
274
  guestfs.should_receive(:sh).with("yum -y remove kernel")
275
- guestfs.should_receive(:sh).with("yum -y install kernel-xen")
275
+ guestfs_helper.should_receive(:sh).with("yum -y install kernel-xen", :arch => 'i686')
276
276
 
277
277
  @plugin.should_receive(:create_devices).with(guestfs)
278
278
  @plugin.should_receive(:upload_fstab).with(guestfs)
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: boxgrinder-build
3
3
  version: !ruby/object:Gem::Version
4
- hash: 53
4
+ hash: 43
5
5
  prerelease: false
6
6
  segments:
7
7
  - 0
8
8
  - 9
9
- - 7
10
- version: 0.9.7
9
+ - 8
10
+ version: 0.9.8
11
11
  platform: ruby
12
12
  authors:
13
13
  - Marek Goldmann
@@ -15,7 +15,7 @@ autorequire:
15
15
  bindir: bin
16
16
  cert_chain: []
17
17
 
18
- date: 2011-09-10 00:00:00 +02:00
18
+ date: 2011-11-15 00:00:00 +01:00
19
19
  default_executable:
20
20
  dependencies:
21
21
  - !ruby/object:Gem::Dependency