diff --git a/lib/oozone/brand_installers/bhyve.rb b/lib/oozone/brand_installers/bhyve.rb index 4eec385..0bae789 100644 --- a/lib/oozone/brand_installers/bhyve.rb +++ b/lib/oozone/brand_installers/bhyve.rb @@ -35,7 +35,99 @@ def create_volume(size, dataset) def write_raw_image(src, dataset) ds_target = "/dev/zvol/dsk/#{dataset}" LOG.info("Copying #{src} to #{ds_target}") - execute!("/bin/pv #{src} > #{ds_target}") + execute!("/bin/cat #{src} >#{ds_target}") + end + + def create_cloudinit_iso + img_dir = create_cloudinit_img_dir + create_cloudinit_iso_from_dir( + img_dir, + conf.metadata[:cloudinit_iso_file] + ) + end + + def fresh_cloudinit_iso_dir(cloudinit_iso_dir = nil) + cloudinit_iso_dir = cloudinit_tmp if cloudinit_iso_dir.nil? + + if cloudinit_iso_dir.exist? + LOG.info("Flushing #{cloudinit_iso_dir}") + FileUtils.rm_rf(cloudinit_iso_dir) + end + + Pathname.new(FileUtils.mkdir_p(cloudinit_iso_dir).first) + rescue StandardError + LOG.error "Cannot create cloud-init dir #{cloudinit_iso_dir}" + nil + end + + # rubocop:disable Metrics/MethodLength + def cloudinit_src_dir(root_dir = Pathname.new('cloud-init'), zone = @zone) + instance_dir = cloudinit_dir(root_dir, zone) + common_dir = cloudinit_dir(root_dir, 'common') + + if instance_dir.exist? + LOG.info "Using image-specific cloud-init config in #{instance_dir}" + instance_dir + elsif common_dir.exist? + LOG.info "Using common cloud-init config in #{common_dir}" + common_dir + else + LOG.error "No cloudinit dir. (Tried #{instance_dir}, #{common_dir}" + nil + end + rescue Errno::ENOENT + LOG.error "Failed to resolve #{root_dir}" + nil + end + # rubocop:enable Metrics/MethodLength + + def create_cloudinit_img_dir(src_dir = nil, target_dir = nil) + src_dir ||= cloudinit_src_dir + target_dir = fresh_cloudinit_iso_dir(target_dir) + + return if target_dir.nil? || src_dir.nil? + + LOG.info("Constructing cloud-init CDROM in #{target_dir}") + + src_dir.children.each do |f| + render_cloudinit_template(f, target_dir.join(f.basename)) + end + + target_dir + end + + def create_cloudinit_iso_from_dir(src_dir, target_file) + cmd = "#{MKISOFS} -output #{target_file} -volid cidata " \ + "-joliet -rock #{src_dir}/" + + execute!(cmd) + end + + private + + def render_cloudinit_template(src, dest) + File.write(dest, ERB.new(File.read(src)).result_with_hash(erb_binding)) + end + + # Just to be explicit + def erb_binding + netconf = @conf.raw[:net].first + { + zone: @zone, + ip_address: netconf[:'allowed-address'], + gateway: netconf[:defrouter], + default_router: netconf[:defrouter] + } + rescue StandardError + raise 'Cannot parse network config for ERB binding' + end + + def cloudinit_tmp + Pathname.new('/tmp').join(conf.metadata[:zone_name]) + end + + def cloudinit_dir(root_dir, zone_name) + root_dir.join(zone_name).realdirpath end end end diff --git a/lib/oozone/commands/create.rb b/lib/oozone/commands/create.rb index e7bdb9e..1339b0d 100644 --- a/lib/oozone/commands/create.rb +++ b/lib/oozone/commands/create.rb @@ -30,6 +30,7 @@ def using_ansible?(conf) end # rubocop:disable Metrics/MethodLength + # rubocop:disable Metrics/AbcSize def action_zone(zone_file) conf = Oozone::ConfigLoader.new(zone_file) zone_name = conf.metadata[:zone_name] @@ -47,7 +48,9 @@ def action_zone(zone_file) zone.boot zone.wait_for_readiness Oozone::Customizer.new(conf).customize! + remove_cloudinit_image(zone_name) if conf.metadata[:cloudinit] end + # rubocop:enable Metrics/AbcSize # rubocop:enable Metrics/MethodLength def install_or_clone @@ -66,6 +69,12 @@ def leave_existing?(zone) false end end + + def remove_cloudinit_image(zone) + LOG.info('Removing cloud-init CD-ROM from zone') + execute!("#{ZONECFG} -z #{zone} 'remove attr name=cdrom'") + execute!("#{ZONECFG} -z #{zone} 'remove fs type=lofs'") + end end end end diff --git a/lib/oozone/controller.rb b/lib/oozone/controller.rb index a478a47..f1c4c25 100644 --- a/lib/oozone/controller.rb +++ b/lib/oozone/controller.rb @@ -92,6 +92,31 @@ def ready? ) == 'online' end + # This is a horrible hack to watch a bhyve zone boot. + # + # rubocop:disable Metrics/MethodLength + # rubocop:disable Metrics/AbcSize + def wait_for_readiness_console + log_name = "#{zone}-#{Time.now.strftime('%Y-%m-%d-%H:%M:%S')}-console.log" + LOG.info 'Waiting for zone to be ready' + File.open(Pathname.new('/var/log').join(log_name), 'w') do |log_file| + PTY.spawn("#{ZLOGIN} -C #{zone}") do |stdout, stdin, _thr| + stdout.each do |line| + log_file.write(line.gsub('s/\e\[[0-9;]*m(?:\e\[K)?', '')) + + stdin.puts "\n" if line.include?('ttyS0') + + if line.include?(' login:') + stdin.puts '~.' + return true + end + end + end + end + end + # rubocop:enable Metrics/AbcSize + # rubocop:enable Metrics/MethodLength + # @return [String, Nil] maybe shouldn't be nil? # def state