cloudfoundry-community/bosh-cloudstack-cpi

View on GitHub
bosh_cli/lib/cli/release_compiler.rb

Summary

Maintainability
C
1 day
Test Coverage
# Copyright (c) 2009-2012 VMware, Inc.

module Bosh::Cli
  # Compiles release tarball based on manifest
  class ReleaseCompiler

    attr_writer :tarball_path

    def self.compile(manifest_file, blobstore)
      new(manifest_file, blobstore).compile
    end

    # @param [String] manifest_file Release manifest path
    # @param [Bosh::Blobstore::Client] blobstore Blobstore client
    # @param [Array] package_matches List of package checksums that director
    #   can match
    # @param [String] release_dir Release directory
    def initialize(manifest_file, blobstore,
                   package_matches = [], release_dir = nil)

      @blobstore = blobstore
      @release_dir = release_dir || Dir.pwd
      @manifest_file = File.expand_path(manifest_file, @release_dir)
      @tarball_path = nil

      @build_dir = Dir.mktmpdir
      @jobs_dir = File.join(@build_dir, "jobs")
      @packages_dir = File.join(@build_dir, "packages")

      @package_matches = Set.new(package_matches)

      FileUtils.mkdir_p(@jobs_dir)
      FileUtils.mkdir_p(@packages_dir)

      @manifest = load_yaml_file(manifest_file)

      @name = @manifest["name"]
      @version = @manifest["version"]
      @packages = @manifest["packages"].map { |pkg| OpenStruct.new(pkg) }
      @jobs = @manifest["jobs"].map { |job| OpenStruct.new(job) }
    end

    def compile
      if exists?
        quit("You already have this version in `#{tarball_path.make_green}'")
      end

      FileUtils.cp(@manifest_file,
                   File.join(@build_dir, "release.MF"),
                   :preserve => true)

      header("Copying packages")
      @packages.each do |package|
        say("#{package.name} (#{package.version})".ljust(30), " ")
        if remote_package_exists?(package)
          say("SKIP".make_yellow)
          next
        end
        package_filename = find_package(package)
        if package_filename.nil?
          err("Cannot find package `#{package.name} (#{package.version})'")
        end
        FileUtils.cp(package_filename,
                     File.join(@packages_dir, "#{package.name}.tgz"),
                     :preserve => true)
      end

      header("Copying jobs")
      @jobs.each do |job|
        say("#{job.name} (#{job.version})".ljust(30), " ")
        if remote_job_exists?(job)
          say("SKIP".make_yellow)
          next
        end
        job_filename = find_job(job)
        if job_filename.nil?
          err("Cannot find job `#{job.name} (#{job.version})")
        end
        FileUtils.cp(job_filename,
                     File.join(@jobs_dir, "#{job.name}.tgz"),
                     :preserve => true)
      end

      header("Building tarball")
      Dir.chdir(@build_dir) do
        tar_out = `tar -czf #{tarball_path} . 2>&1`
        unless $?.exitstatus == 0
          raise InvalidRelease, "Cannot create release tarball: #{tar_out}"
        end
        say("Generated #{tarball_path.make_green}")
        say("Release size: #{pretty_size(tarball_path).make_green}")
      end
    end

    def exists?
      File.exists?(tarball_path)
    end

    def tarball_path
      @tarball_path || File.join(File.dirname(@manifest_file),
                                 "#{@name}-#{@version}.tgz")
    end

    def find_package(package)
      final_index = VersionsIndex.new(
          File.join(@release_dir, ".final_builds", "packages", package.name))
      dev_index = VersionsIndex.new(
          File.join(@release_dir, ".dev_builds", "packages", package.name))
      find_in_indices(final_index, dev_index, package)
    end

    def find_job(job)
      final_index = VersionsIndex.new(
          File.join(@release_dir, ".final_builds", "jobs", job.name))
      dev_index = VersionsIndex.new(
          File.join(@release_dir, ".dev_builds", "jobs", job.name))
      find_in_indices(final_index, dev_index, job)
    end

    def find_in_indices(final_index, dev_index, object)
      desc = "#{object.name} (#{object.version})"

      index = final_index
      build_data = index.find_by_checksum(object.sha1)

      if build_data.nil?
        index = dev_index
        build_data = index.find_by_checksum(object.sha1)
      end

      if build_data.nil?
        say("MISSING".make_red)
        err("Cannot find object with given checksum")
      end

      version = build_data["version"]
      sha1 = build_data["sha1"]
      blobstore_id = build_data["blobstore_id"]
      filename = index.filename(version)

      if File.exists?(filename)
        say("FOUND LOCAL".make_green)
        if Digest::SHA1.file(filename) != sha1
          err("#{desc} is corrupted locally")
        end
      elsif blobstore_id
        say("FOUND REMOTE".make_yellow)
        say("Downloading #{blobstore_id.to_s.make_green}...")
        tmp_file = Tempfile.new("")
        @blobstore.get(blobstore_id, tmp_file)
        tmp_file.close

        if Digest::SHA1.file(tmp_file.path).hexdigest == sha1
          FileUtils.mv(tmp_file.path, filename)
        else
          err("#{desc} is corrupted in blobstore (id=#{blobstore_id})")
        end
      end

      File.exists?(filename) ? filename : nil

    rescue Bosh::Blobstore::BlobstoreError => e
      raise BlobstoreError, "Blobstore error: #{e}"
    end

    # Checks if local package is already known remotely
    # @param [#name, #version] local_package
    # @return [Boolean]
    def remote_package_exists?(local_package)
      # If checksum is known to director we can always match it
      @package_matches.include?(local_package.sha1) ||
        (local_package.fingerprint &&
         @package_matches.include?(local_package.fingerprint))
    end

    # Checks if local job is already known remotely
    # @param [#name, #version] local_job
    # @return [Boolean]
    def remote_job_exists?(local_job)
      false
    end
  end

end