class Fig::RepositoryPackagePublisher

Handles package publishing for the Repository.

Attributes

application_configuration[W]
base_temp_dir[W]
descriptor[W]
local_directory_for_package[W]
local_fig_file_for_package[W]
local_only[W]
operating_system[W]
options[W]
publish_listeners[W]
remote_directory_for_package[W]
remote_fig_file_for_package[W]
runtime_for_package[W]
source_package[W]
was_forced[W]

Public Class Methods

new() click to toggle source
# File lib/fig/repository_package_publisher.rb, line 47
def initialize()
  @text_assembler =
    Fig::PackageDefinitionTextAssembler.new :emit_as_to_be_published

  return
end

Public Instance Methods

package_statements=(statements) click to toggle source
# File lib/fig/repository_package_publisher.rb, line 54
def package_statements=(statements)
  @text_assembler.add_input(statements)
end
publish_package() click to toggle source
# File lib/fig/repository_package_publisher.rb, line 58
def publish_package()
  derive_publish_metadata()

  temp_dir = publish_temp_dir()
  @operating_system.delete_and_recreate_directory(temp_dir)
  @operating_system.delete_and_recreate_directory(
    @local_directory_for_package
  )
  @operating_system.delete_and_recreate_directory(@runtime_for_package)

  fig_file = File.join(temp_dir, Fig::Repository::PACKAGE_FILE_IN_REPO)
  content, published_package =
    derive_definition_file_and_create_resource_archive
  @operating_system.write(fig_file, content)

  publish_package_contents
  if not @local_only
    @operating_system.upload(fig_file, @remote_fig_file_for_package)
  end
  @operating_system.copy(fig_file, @local_fig_file_for_package)

  notify_listeners

  FileUtils.rm_rf(temp_dir)

  check_published_environment_variables published_package

  return true
end

Private Instance Methods

add_asset_to_output_statements(asset_statement) click to toggle source
# File lib/fig/repository_package_publisher.rb, line 410
def add_asset_to_output_statements(asset_statement)
  if Fig::URL.is_url? asset_statement.location
    add_output_asset_statement_based_upon_input_statement(
      asset_statement, asset_statement
    )
  elsif asset_statement.is_a? Fig::Statement::Archive
    if asset_statement.requires_globbing?
      expand_globs_from( [asset_statement.location] ).each do
        |file|

        add_output_asset_statement_based_upon_input_statement(
          Fig::Statement::Archive.new(
            nil,
            %Q<[synthetic statement created in #{__FILE__} line #{__LINE__}]>,
            file,
            false # No globbing
          ),
          asset_statement
        )
      end
    else
      add_output_asset_statement_based_upon_input_statement(
        asset_statement, asset_statement
      )
    end
  elsif asset_statement.requires_globbing?
    @local_resource_paths.concat expand_globs_from(
      [asset_statement.location]
    )
  else
    @local_resource_paths << asset_statement.location
  end

  return
end
add_environment_variables_to_package_metadata() click to toggle source
# File lib/fig/repository_package_publisher.rb, line 236
def add_environment_variables_to_package_metadata()
  variables = @application_configuration[
    'environment variables to include in comments in published packages'
  ]
  return if ! variables || variables.empty?

  @text_assembler.add_header %q<#>
  @text_assembler.add_header %q<# Values of some environment variables at time of publish:>
  @text_assembler.add_header %q<#>
  variables.each do
    |variable|

    value = ENV[variable]
    if value.nil?
      value = ' was unset.'
    else
      value = "=#{value}"
    end

    @text_assembler.add_header %Q<#     #{variable}#{value}>
  end

  return
end
add_git_metadata_to_package_metadata() click to toggle source
# File lib/fig/repository_package_publisher.rb, line 327
def add_git_metadata_to_package_metadata()
  url = get_git_origin_url or return
  url.strip!
  return if url.empty?

  branch = get_git_branch
  if branch.nil?
    branch = ''
  else
    branch = ", branch #{branch}"
  end
  sha1 = get_git_sha1
  if sha1.nil?
    sha1 = ''
  else
    sha1 = ",\n# SHA1 #{sha1}"
  end

  @text_assembler.add_header %q<#>
  @text_assembler.add_header(
    %Q<# Publish happened in a Git working directory from\n# #{url}#{branch}#{sha1}.>
  )

  return
end
add_output_asset_statement_based_upon_input_statement( output_statement, input_statement ) click to toggle source
# File lib/fig/repository_package_publisher.rb, line 446
def add_output_asset_statement_based_upon_input_statement(
  output_statement, input_statement
)
  @text_assembler.add_output output_statement

  asset_name = output_statement.asset_name

  input_statements =
    @asset_names_with_associated_input_statements[asset_name]
  if input_statements.nil?
    input_statements = []

    @asset_names_with_associated_input_statements[asset_name] =
      input_statements
  end

  input_statements << input_statement

  return
end
add_package_metadata_comments() click to toggle source
# File lib/fig/repository_package_publisher.rb, line 152
def add_package_metadata_comments()
  add_publish_comment

  @text_assembler.add_header(
    %Q<# Publishing information for #{@descriptor.to_string()}:>
  )
  @text_assembler.add_header %q<#>

  @text_assembler.add_header(
    %Q<#     Time: #{@publish_time} (epoch: #{@publish_time.to_i()})>
  )

  @text_assembler.add_header %Q<#     User: #{@publish_login}>
  @text_assembler.add_header %Q<#     Host: #{@publish_host}>
  @text_assembler.add_header %Q<#     O/S:  #{derive_platform_description}>

  sanitized_argv = ARGV.map {|arg| arg.gsub "\n", '\\n'}
  @text_assembler.add_header %Q<#     Args: "#{sanitized_argv.join %q[", "]}">

  @text_assembler.add_header %Q<#     Fig:  v#{Fig::VERSION}>

  add_environment_variables_to_package_metadata
  add_version_control_to_package_metadata

  @text_assembler.add_header %Q<\n>

  return
end
add_publish_comment() click to toggle source
# File lib/fig/repository_package_publisher.rb, line 181
def add_publish_comment
  return if not @options.publish_comment and not @options.publish_comment_path

  if @options.publish_comment
    comment = @options.publish_comment
    comment = comment.strip.gsub(/[ \t]*\n/, "\n# ").gsub(/^#[ ]+\n/, "#\n")
    @text_assembler.add_header %Q<# #{comment}>
    @text_assembler.add_header %q<#>
  end

  if @options.publish_comment_path
    begin
      comment = IO.read(
        @options.publish_comment_path, :external_encoding => Encoding::UTF_8,
      )
      comment = comment.strip.gsub(/[ \t]*\n/, "\n# ").gsub(/^#[ ]+\n/, "#\n")
    rescue Errno::ENOENT
      Fig::Logging.fatal(
        %Q<Comment file "#{@options.publish_comment_path}" does not exist.>
      )
      raise Fig::RepositoryError.new
    rescue Errno::EACCES
      Fig::Logging.fatal(
        %Q<Could not read comment file "#{@options.publish_comment_path}".>
      )
      raise Fig::RepositoryError.new
    end

    @text_assembler.add_header %Q<# #{comment}>
    @text_assembler.add_header %q<#>
  end

  @text_assembler.add_header %q<#>

  return
end
add_subversion_metadata_to_package_metadata() click to toggle source
# File lib/fig/repository_package_publisher.rb, line 270
def add_subversion_metadata_to_package_metadata()
  output = get_subversion_working_directory_info
  return if not output =~ /^URL: +(.*\S)\s*$/
  url = $1

  revision = ''
  if output =~ /^Revision: +(\S+)\s*$/
    revision = ", revision #{$1}"
  end

  @text_assembler.add_header %q<#>
  @text_assembler.add_header(
    %Q<# Publish happened in a Subversion working directory from\n# #{url}#{revision}.>
  )

  return
end
add_unparsed_text() click to toggle source
# File lib/fig/repository_package_publisher.rb, line 596
def add_unparsed_text()
  if @source_package && @source_package.unparsed_text
    @text_assembler.add_footer ''
    @text_assembler.add_footer '# Original, unparsed package text:'
    @text_assembler.add_footer '#'
    @text_assembler.add_footer(
      @source_package.unparsed_text.gsub(/^(?=[^\n]+$)/, '# ').gsub(/^$/, '#')
    )
  end

  return
end
add_version_control_to_package_metadata() click to toggle source
# File lib/fig/repository_package_publisher.rb, line 261
def add_version_control_to_package_metadata()
  return if @options.suppress_vcs_comments_in_published_packages?

  add_subversion_metadata_to_package_metadata()
  add_git_metadata_to_package_metadata()

  return
end
assemble_output_statements_and_accumulate_assets() click to toggle source
# File lib/fig/repository_package_publisher.rb, line 393
def assemble_output_statements_and_accumulate_assets()
  @local_resource_paths = []
  @asset_names_with_associated_input_statements = {}

  @text_assembler.input_statements.each do
    |statement|

    if statement.is_asset?
      add_asset_to_output_statements(statement)
    else
      @text_assembler.add_output statement
    end
  end

  return
end
check_asset_path(asset_path) click to toggle source
# File lib/fig/repository_package_publisher.rb, line 637
def check_asset_path(asset_path)
  if not File.exist?(asset_path)
    Fig::Logging.fatal "Could not find file #{asset_path}."
    raise Fig::RepositoryError.new
  end

  return
end
check_asset_paths(asset_paths) click to toggle source
# File lib/fig/repository_package_publisher.rb, line 646
def check_asset_paths(asset_paths)
  non_existing_paths =
    asset_paths.select {|path| ! File.exist?(path) && ! File.symlink?(path) }

  if not non_existing_paths.empty?
    if non_existing_paths.size > 1
      Fig::Logging.fatal "Could not find files: #{ non_existing_paths.join(', ') }"
    else
      Fig::Logging.fatal "Could not find file #{non_existing_paths[0]}."
    end

    raise Fig::RepositoryError.new
  end

  return
end
check_published_environment_variables(published_package) click to toggle source
# File lib/fig/repository_package_publisher.rb, line 663
def check_published_environment_variables(published_package)
  published_package.walk_statements do
    |statement|

    if statement.is_environment_variable?
      tokenized_value = statement.tokenized_value
      expansion_happened = false
      expanded_value = tokenized_value.to_expanded_string {
        expansion_happened = true; published_package.runtime_directory
      }

      if expansion_happened && ! File.exists?(expanded_value) && ! File.symlink?(expanded_value)
        Fig::Logging.warn(
          %Q<The #{statement.name} variable points to a path that does not exist (#{expanded_value}); retrieve statements that are active when this package is included may fail.>
        )
      end
    end
  end

  return
end
collective_position_string(statements) click to toggle source
# File lib/fig/repository_package_publisher.rb, line 509
def collective_position_string(statements)
  return (
    statements.map {
      |statement|

      position_string = statement.position_string

      position_string.empty? ? '<unknown>' : position_string.strip
    }
  ).join(', ')
end
create_resource_archive() click to toggle source
# File lib/fig/repository_package_publisher.rb, line 521
def create_resource_archive()
  if @local_resource_paths.size > 0
    check_asset_paths(@local_resource_paths)

    file = File.join publish_temp_dir, Fig::Repository::RESOURCES_FILE
    @operating_system.create_archive(file, @local_resource_paths)
    Fig::AtExit.add { FileUtils.rm_f(file) }

    @text_assembler.add_output(
      Fig::Statement::SyntheticRawText.new(nil, nil, "\n"),
      Fig::Statement::Archive.new(
        nil,
        %Q<[synthetic statement created in #{__FILE__} line #{__LINE__}]>,
        file,
        false # No globbing
      )
    )
  end

  return
end
derive_definition_file_and_create_resource_archive() click to toggle source
# File lib/fig/repository_package_publisher.rb, line 119
def derive_definition_file_and_create_resource_archive()
  add_package_metadata_comments()
  assemble_output_statements_and_accumulate_assets()
  validate_asset_names()
  create_resource_archive()
  add_unparsed_text()

  file_content, explanations = @text_assembler.assemble_package_definition()
  if Fig::Logging.info?
    explanations.each {|explanation| Fig::Logging.info explanation}
  end

  to_be_published_package = nil
  begin
    unparsed_package = Fig::NotYetParsedPackage.new
    unparsed_package.descriptor         = @descriptor
    unparsed_package.working_directory  =
      unparsed_package.include_file_base_directory =
      @runtime_for_package
    unparsed_package.source_description = '<package to be published>'
    unparsed_package.unparsed_text      = file_content

    to_be_published_package =
      Fig::Parser.new(nil, false).parse_package(unparsed_package)
  rescue Fig::PackageParseError => error
    raise \
      "Bug in code! Could not parse package definition to be published.\n" +
      "#{error}\n\nGenerated contents:\n#{file_content}"
  end

  return file_content, to_be_published_package
end
derive_login() click to toggle source
# File lib/fig/repository_package_publisher.rb, line 98
def derive_login()
  # Etc.getpwuid() returns nil on Windows, but Etc.getlogin() will return the
  # wrong result on some systems if you su(1) to a different user.

  password_entry = nil

  begin
    password_entry = Etc.getpwuid()
  rescue ArgumentError
    # This should come from
    # https://github.com/ruby/ruby/blob/de5e6ca2b484dc19d007ed26b2a923a9a4b34f77/ext/etc/etc.c#L191,
    # which we will silently fall back to .getlogin() for.
  end

  if password_entry and password_entry.name
    return password_entry.name
  end

  return Etc.getlogin()
end
derive_platform_description() click to toggle source
# File lib/fig/repository_package_publisher.rb, line 218
def derive_platform_description
  host_os = RbConfig::CONFIG['host_os']

  return host_os if host_os !~ /linux|darwin/i

  if host_os =~ /darwin/i
    product_name = %x/sw_vers -productName/
    product_version = %x/sw_vers -productVersion/
    return product_name.strip + ' ' + product_version.strip
  end

  linux_distribution = %x/lsb_release --description --short/
  linux_distribution.chomp!
  linux_distribution.gsub!(/\A " (.*) " \z/x, '\1')

  return linux_distribution
end
derive_publish_metadata() click to toggle source
# File lib/fig/repository_package_publisher.rb, line 90
def derive_publish_metadata()
  @publish_time  = Time.now()
  @publish_login = derive_login()
  @publish_host  = Socket.gethostname()

  return
end
expand_globs_from(paths) click to toggle source

'paths' is an Array of fileglob patterns: ['tmp/foo/file1', 'tmp/foo/*.jar']

# File lib/fig/repository_package_publisher.rb, line 687
def expand_globs_from(paths)
  expanded_files = []

  paths.each do
    |path|

    globbed_files = Dir.glob(path)
    if globbed_files.empty?
      expanded_files << path
    else
      expanded_files.concat(globbed_files)
    end
  end

  return expanded_files
end
get_git_branch() click to toggle source
# File lib/fig/repository_package_publisher.rb, line 363
def get_git_branch()
  executable =
    get_version_control_executable('FIG_GIT_EXECUTABLE', 'git') or return
  reference = run_version_control_command(
    [executable, 'rev-parse', '--abbrev-ref=strict', 'HEAD'],
    'Git',
    'FIG_GIT_EXECUTABLE'
  )
  return if reference.nil?

  reference.strip!
  return if reference.empty?

  return reference
end
get_git_origin_url() click to toggle source
# File lib/fig/repository_package_publisher.rb, line 353
def get_git_origin_url()
  executable =
    get_version_control_executable('FIG_GIT_EXECUTABLE', 'git') or return
  return run_version_control_command(
    [executable, 'config', '--get', 'remote.origin.url'],
    'Git',
    'FIG_GIT_EXECUTABLE'
  )
end
get_git_sha1() click to toggle source
# File lib/fig/repository_package_publisher.rb, line 379
def get_git_sha1()
  executable =
    get_version_control_executable('FIG_GIT_EXECUTABLE', 'git') or return
  reference = run_version_control_command(
    [executable, 'rev-parse', 'HEAD'], 'Git', 'FIG_GIT_EXECUTABLE'
  )
  return if reference.nil?

  reference.strip!
  return if reference.empty?

  return reference
end
get_subversion_working_directory_info() click to toggle source
# File lib/fig/repository_package_publisher.rb, line 288
def get_subversion_working_directory_info()
  executable =
    get_version_control_executable('FIG_SVN_EXECUTABLE', 'svn') or return
  return run_version_control_command(
    [executable, 'info'], 'Subversion', 'FIG_SVN_EXECUTABLE'
  )
end
get_version_control_executable(variable, default) click to toggle source
# File lib/fig/repository_package_publisher.rb, line 317
def get_version_control_executable(variable, default)
  executable = ENV[variable]
  if ! executable || executable.empty? || executable =~ /\A\s*\z/
    return if ENV.include? variable
    return default
  end

  return executable
end
notify_listeners() click to toggle source
# File lib/fig/repository_package_publisher.rb, line 609
def notify_listeners()
  publish_information = {}
  publish_information[:descriptor]          = @descriptor
  publish_information[:time]                = @publish_time
  publish_information[:login]               = @publish_login
  publish_information[:host]                = @publish_host

  # Ensure that we've really got booleans and not merely true or false
  # values.
  publish_information[:was_forced]          = @was_forced ? true : false
  publish_information[:local_only]          = @local_only ? true : false

  publish_information[:local_destination]   = @local_directory_for_package
  publish_information[:remote_destination]  = @remote_directory_for_package

  @publish_listeners.each do
    |listener|

    listener.published(publish_information)
  end

  return
end
publish_asset(asset_statement) click to toggle source
# File lib/fig/repository_package_publisher.rb, line 555
def publish_asset(asset_statement)
  asset_name = asset_statement.asset_name()

  if Fig::URL.is_url? asset_statement.location
    asset_local = File.join(publish_temp_dir(), asset_name)

    begin
      @operating_system.download(asset_statement.location, asset_local, false)
    rescue Fig::FileNotFoundError
      Fig::Logging.fatal "Could not download #{asset_statement.location}."
      raise Fig::RepositoryError.new
    end
  else
    asset_local = asset_statement.location
    check_asset_path(asset_local)
  end

  if not @local_only
    asset_remote = Fig::URL.append_path_components(
      @remote_directory_for_package, [asset_name],
    )

    @operating_system.upload(asset_local, asset_remote)
  end

  @operating_system.copy(
    asset_local, @local_directory_for_package + '/' + asset_name
  )
  if asset_statement.is_a?(Fig::Statement::Archive)
    @operating_system.unpack_archive(
      @runtime_for_package, File.absolute_path(asset_local)
    )
  else
    @operating_system.copy(
      asset_local, @runtime_for_package + '/' + asset_name
    )
  end

  return
end
publish_package_contents() click to toggle source
# File lib/fig/repository_package_publisher.rb, line 543
def publish_package_contents()
  @text_assembler.output_statements.each do
    |statement|

    if statement.is_asset?
      publish_asset(statement)
    end
  end

  return
end
publish_temp_dir() click to toggle source
# File lib/fig/repository_package_publisher.rb, line 633
def publish_temp_dir()
  File.join(@base_temp_dir, 'publish')
end
run_version_control_command(command, version_control_name, variable) click to toggle source
# File lib/fig/repository_package_publisher.rb, line 296
def run_version_control_command(command, version_control_name, variable)
  begin
    output, errors, result = Fig::ExternalProgram.capture command
  rescue Errno::ENOENT => error
    Fig::Logging.warn(
      %Q<Could not run "#{command.join ' '}": #{error.message}. Set #{variable} to the path to use for #{version_control_name} or to the empty string to suppress #{version_control_name} support.>
    )
    return
  end

  if result && ! result.success?
    Fig::Logging.debug(
      %Q<Could not run "#{command.join ' '}": #{result}: #{errors}>
    )

    return
  end

  return output
end
validate_asset_names() click to toggle source
# File lib/fig/repository_package_publisher.rb, line 467
def validate_asset_names()
  found_problem = false

  @asset_names_with_associated_input_statements.keys.sort.each do
    |asset_name|

    statements = @asset_names_with_associated_input_statements[asset_name]

    if asset_name == Fig::Repository::RESOURCES_FILE
      Fig::Logging.fatal \
        %Q<You cannot have an asset with the name "#{Fig::Repository::RESOURCES_FILE}"#{collective_position_string(statements)} due to Fig implementation details.>
      found_problem = true
    end

    archive_statements =
      statements.select { |s| s.is_a? Fig::Statement::Archive }
    if (
          not archive_statements.empty? \
      and not (
            asset_name =~ /\.tar\.gz$/    \
        or  asset_name =~ /\.tgz$/        \
        or  asset_name =~ /\.tar\.bz2$/   \
        or  asset_name =~ /\.zip$/
      )
    )
      Fig::Logging.fatal \
        %Q<Unknown archive type "#{asset_name}"#{collective_position_string(archive_statements)}.>
      found_problem = true
    end

    if statements.size > 1
      Fig::Logging.fatal \
        %Q<Found multiple assets with the name "#{asset_name}"#{collective_position_string(statements)}. If these were allowed, assets would overwrite each other.>
      found_problem = true
    end
  end

  if found_problem
      raise Fig::RepositoryError.new
  end
end