require 'spec_helper'
require 'bosh/stemcell/builder_command'

module Bosh::Stemcell
  describe BuilderCommand do
    subject(:stemcell_builder_command) do
      described_class.new(
        env,
        infrastructure_name: infrastructure.name,
        operating_system_name: operating_system.name,
        agent_name: agent_name,
        release_tarball_path: release_tarball_path,
        version: version
      )
    end

    let(:env) { {} }

    let(:agent_name) { 'ruby' }
    let(:expected_agent_name) { 'ruby' }

    let(:infrastructure) do
      instance_double(
        'Bosh::Stemcell::Infrastructure::Vsphere',
        name: 'vsphere',
        hypervisor: 'esxi'
      )
    end

    let(:operating_system) { instance_double('Bosh::Stemcell::OperatingSystem::Ubuntu', name: 'ubuntu') }
    let(:release_tarball_path) { "/fake/path/to/bosh-#{version}.tgz" }
    let(:version) { '007' }

    before do
      Infrastructure.stub(:for).with('vsphere').and_return(infrastructure)
      OperatingSystem.stub(:for).with(operating_system.name).and_return(operating_system)
      StageCollection.stub(:new).with(
        infrastructure: infrastructure,
        operating_system: operating_system,
        agent_name: agent_name,
      ).and_return(stage_collection)

      StageRunner.stub(:new).with(
        build_path: File.join(root_dir, 'build', 'build'),
        command_env: 'env ',
        settings_file: settings_file,
        work_path: File.join(root_dir, 'work')
      ).and_return(stage_runner)

      BuilderOptions.stub(:new).with(
        env,
        tarball: release_tarball_path,
        stemcell_version: version,
        infrastructure: infrastructure,
        operating_system: operating_system,
        agent_name: expected_agent_name,
      ).and_return(stemcell_builder_options)
    end

    let(:root_dir) do
      File.join('/mnt/stemcells', infrastructure.name, infrastructure.hypervisor, operating_system.name)
    end

    let(:stemcell_builder_options) do
      instance_double('Bosh::Stemcell::BuilderOptions', default: options)
    end

    let(:stage_collection) do
      instance_double(
        'Bosh::Stemcell::StageCollection',
        all_stages: %w(FAKE_OS_STAGES FAKE_INFRASTRUCTURE_STAGES)
      )
    end

    let(:stage_runner) { instance_double('Bosh::Stemcell::StageRunner', configure_and_apply: nil) }

    let(:etc_dir) { File.join(root_dir, 'build', 'build', 'etc') }
    let(:settings_file) { File.join(etc_dir, 'settings.bash') }

    let(:options) do
      {
        'hello'               => 'world',
        'stemcell_tgz'        => 'fake-stemcell.tgz',
        'stemcell_image_name' => 'fake-root-disk-image.raw'
      }
    end

    its(:chroot_dir) { should eq(File.join(root_dir, 'work', 'work', 'chroot')) }

    describe '#build' do
      include FakeFS::SpecHelpers

      before do
        Process.stub(pid: 99999)

        stemcell_builder_command.stub(system: true)
        FileUtils.touch('leftover.tgz')

        FileUtils.stub(:cp_r).with([], File.join(root_dir, 'build', 'build'), preserve: true, verbose: true) do
          FileUtils.mkdir_p(etc_dir)
          FileUtils.touch(settings_file)
        end
      end

      context 'when agent_name is nil' do
        let(:agent_name) { nil }
        let(:expected_agent_name) { 'ruby' }

        it 'uses the correct agent name for the stage collection' do
          StageCollection.should_receive(:new).with(
            infrastructure: infrastructure,
            operating_system: operating_system,
            agent_name: expected_agent_name,
          ).and_return(stage_collection)

          stemcell_builder_command.build
        end
      end

      context 'when agent_name is foo' do
        let(:agent_name) { 'foo' }
        let(:expected_agent_name) { 'foo' }

        it 'uses the correct agent name for the stage collection' do
          StageCollection.should_receive(:new).with(
            infrastructure: infrastructure,
            operating_system: operating_system,
            agent_name: expected_agent_name,
          ).and_return(stage_collection)

          stemcell_builder_command.build
        end
      end

      describe 'sanitizing the environment' do
        it 'removes any tgz files from current working directory' do
          expect {
            stemcell_builder_command.build
          }.to change { Dir.glob('*.tgz').size }.to(0)
        end

        it 'unmounts the disk image used to install Grub' do
          image_path = File.join(root_dir, 'work/work/mnt/tmp/grub/fake-root-disk-image.raw')
          unmount_img_command = "sudo umount #{image_path} 2> /dev/null"
          stemcell_builder_command.should_receive(:system).with(unmount_img_command)
          stemcell_builder_command.build
        end

        it 'unmounts work/work/mnt directory' do
          unmount_dir_command = "sudo umount #{File.join(root_dir, 'work/work/mnt')} 2> /dev/null"
          stemcell_builder_command.should_receive(:system).with(unmount_dir_command)
          stemcell_builder_command.build
        end

        it 'removes stemcell root directory' do
          stemcell_builder_command.should_receive(:system).with("sudo rm -rf #{root_dir}")
          stemcell_builder_command.build
        end
      end

      it 'returns the full path of the generated stemcell archive' do
        expect(stemcell_builder_command.build).to eq(File.join(root_dir, 'work', 'work', 'fake-stemcell.tgz'))
      end

      it 'creates a base directory for stemcell creation' do
        expect {
          stemcell_builder_command.build
        }.to change { Dir.exists?(root_dir) }.from(false).to(true)
      end

      it 'creates a build directory for stemcell creation' do
        expect {
          stemcell_builder_command.build
        }.to change { Dir.exists?(File.join(root_dir, 'build')) }.from(false).to(true)
      end

      it 'copies the stemcell_builder code into the build directory' do
        FileUtils.should_receive(:cp_r).with([],
                                             File.join(root_dir, 'build', 'build'),
                                             preserve: true,
                                             verbose: true) do
          FileUtils.mkdir_p(etc_dir)
          FileUtils.touch(settings_file)
        end

        stemcell_builder_command.build
      end

      it 'creates a work directory for stemcell creation chroot' do
        expect {
          stemcell_builder_command.build
        }.to change { Dir.exists?(File.join(root_dir, 'work')) }.from(false).to(true)
      end

      it 'writes a settings file into the build directory' do
        stemcell_builder_command.build

        expect(File.read(settings_file)).to match(/hello=world/)
      end

      describe 'running stages' do
        let(:expected_rspec_command) do
          [
            "cd #{File.expand_path('../../..', File.dirname(__FILE__))};",
            "STEMCELL_IMAGE=#{File.join(root_dir, 'work', 'work', 'fake-root-disk-image.raw')}",
            'bundle exec rspec -fd',
            "spec/stemcells/#{operating_system.name}_spec.rb",
            "spec/stemcells/#{agent_name}_agent_spec.rb",
            "spec/stemcells/#{infrastructure.name}_spec.rb",
          ].join(' ')
        end

        it 'calls #configure_and_apply' do
          stage_runner.should_receive(:configure_and_apply).
            with(%w(FAKE_OS_STAGES FAKE_INFRASTRUCTURE_STAGES)).ordered
          stemcell_builder_command.should_receive(:system).
            with(expected_rspec_command).ordered

          stemcell_builder_command.build
        end

        context 'with CentOS' do
          let(:operating_system) { instance_double('Bosh::Stemcell::OperatingSystem::Centos', name: 'centos') }

          it 'calls #configure_and_apply' do
            stage_runner.should_receive(:configure_and_apply).
              with(%w(FAKE_OS_STAGES FAKE_INFRASTRUCTURE_STAGES)).ordered
            stemcell_builder_command.should_receive(:system).
              with(expected_rspec_command).ordered

            stemcell_builder_command.build
          end
        end
      end

      context 'when ENV contains variables besides HTTP_PROXY and NO_PROXY' do
        let(:env) do
          {
            'NOT_HTTP_PROXY' => 'nice_proxy',
            'no_proxy_just_kidding' => 'naughty_proxy'
          }
        end

        it 'nothing is passed to sudo via "env"' do
          StageRunner.stub(:new).with(build_path: File.join(root_dir, 'build', 'build'),
                                      command_env: 'env ',
                                      settings_file: settings_file,
                                      work_path: File.join(root_dir, 'work')).and_return(stage_runner)

          stemcell_builder_command.build
        end
      end

      context 'ENV variables for HTTP_PROXY and NO_PROXY are passed to "env"' do
        let(:env) do
          {
            'HTTP_PROXY' => 'nice_proxy',
            'no_proxy' => 'naughty_proxy'
          }
        end

        it 'they are passed to sudo via "env"' do
          StageRunner.stub(:new).with(build_path: File.join(root_dir, 'build', 'build'),
                                      command_env: "env HTTP_PROXY='nice_proxy' no_proxy='naughty_proxy'",
                                      settings_file: settings_file,
                                      work_path: File.join(root_dir, 'work')).and_return(stage_runner)

          stemcell_builder_command.build
        end
      end
    end
  end
end
