class Habitat::Profile

def self.create(path, options = {})

def self.create(path, options = {})
  creator = new(path, options)
  hart_file = creator.create
  creator.copy(hart_file)
ensure
  creator.delete_work_dir
end

def self.setup(path)

def self.setup(path)
  new(path).setup
end

def self.upload(path, options = {})

def self.upload(path, options = {})
  uploader = new(path, options)
  uploader.upload
ensure
  uploader.delete_work_dir
end

def build_hart

def build_hart
  Habitat::Log.info('Building our Habitat artifact...')
  env = {
    'TERM'               => 'vt100',
    'HAB_ORIGIN'         => habitat_origin,
    'HAB_NONINTERACTIVE' => 'true',
  }
  env['RUST_LOG'] = 'debug' if Habitat::Log.level == :debug
  # TODO: Would love to use Mixlib::ShellOut here, but it doesn't
  # seem to preserve the STDIN tty, and docker gets angry.
  Dir.chdir(work_dir) do
    unless system(env, 'hab pkg build .')
      exit_with_error('Unable to build the Habitat artifact.')
    end
  end
  hart_files = Dir.glob(File.join(work_dir, 'results', '*.hart'))
  if hart_files.length > 1
    exit_with_error('More than one Habitat artifact was created which was not expected.')
  elsif hart_files.empty?
    exit_with_error('No Habitat artifact was created.')
  end
  hart_files.first
end

def copy(hart_file)

def copy(hart_file)
  validate_output_dir
  Habitat::Log.info("Copying artifact to #{output_dir}...")
  copy_hart(hart_file)
end

def copy_hart(working_dir_hart)

def copy_hart(working_dir_hart)
  hart_basename = File.basename(working_dir_hart)
  dst = File.join(output_dir, hart_basename)
  FileUtils.cp(working_dir_hart, dst)
  dst
end

def copy_profile_to_work_dir

def copy_profile_to_work_dir
  Habitat::Log.info('Copying profile contents to the work directory...')
  profile.files.each do |f|
    src = File.join(profile.root_path, f)
    dst = File.join(work_dir, f)
    if File.directory?(f)
      Habitat::Log.debug("Creating directory #{dst}")
      FileUtils.mkdir_p(dst)
    else
      Habitat::Log.debug("Copying file #{src} to #{dst}")
      FileUtils.cp_r(src, dst)
    end
  end
end

def create

def create
  Habitat::Log.info("Creating a Habitat artifact for profile: #{path}")
  validate_habitat_installed
  validate_habitat_origin
  create_profile_object
  verify_profile
  vendor_profile_dependencies
  copy_profile_to_work_dir
  create_habitat_directories(work_dir)
  create_plan(work_dir)
  create_run_hook(work_dir)
  create_settings_file(work_dir)
  create_default_config(work_dir)
  # returns the path to the .hart file in the work directory
  build_hart
rescue => e
  Habitat::Log.debug(e.backtrace.join("\n"))
  exit_with_error(
    'Unable to generate Habitat artifact.',
    "#{e.class} -- #{e.message}",
  )
end

def create_default_config(directory)

def create_default_config(directory)
  default_toml = File.join(directory, 'habitat', 'default.toml')
  Habitat::Log.info("Generating Habitat's default.toml configuration...")
  File.write(default_toml, 'sleep_time = 300')
end

def create_habitat_directories(parent_directory)

def create_habitat_directories(parent_directory)
  [
    File.join(parent_directory, 'habitat'),
    File.join(parent_directory, 'habitat', 'config'),
    File.join(parent_directory, 'habitat', 'hooks'),
  ].each do |dir|
    Dir.mkdir(dir) unless Dir.exist?(dir)
  end
end

def create_plan(directory)

def create_plan(directory)
  plan_file = File.join(directory, 'habitat', 'plan.sh')
  Habitat::Log.info("Generating Habitat plan at #{plan_file}...")
  File.write(plan_file, plan_contents)
end

def create_profile_object

def create_profile_object
  @profile = Inspec::Profile.for_target(
    path,
    backend: Inspec::Backend.create(target: 'mock://'),
  )
end

def create_run_hook(directory)

def create_run_hook(directory)
  run_hook_file = File.join(directory, 'habitat', 'hooks', 'run')
  Habitat::Log.info("Generating a Habitat run hook at #{run_hook_file}...")
  File.write(run_hook_file, run_hook_contents)
end

def create_settings_file(directory)

def create_settings_file(directory)
  settings_file = File.join(directory, 'habitat', 'config', 'settings.sh')
  Habitat::Log.info("Generating a settings file at #{settings_file}...")
  File.write(settings_file, "SLEEP_TIME={{cfg.sleep_time}}\n")
end

def delete_work_dir

def delete_work_dir
  Habitat::Log.debug("Deleting work directory #{work_dir}")
  FileUtils.rm_rf(work_dir) if Dir.exist?(work_dir)
end

def exit_with_error(*errors)

def exit_with_error(*errors)
  errors.each do |error_msg|
    Habitat::Log.error(error_msg)
  end
  exit 1
end

def habitat_auth_token

def habitat_auth_token
  ENV['HAB_AUTH_TOKEN'] || habitat_cli_config['auth_token']
end

def habitat_cli_config

def habitat_cli_config
  return @cli_config if @cli_config
  config_file = File.join(ENV['HOME'], '.hab', 'etc', 'cli.toml')
  return {} unless File.exist?(config_file)
  @cli_config = Tomlrb.load_file(config_file)
end

def habitat_origin

def habitat_origin
  ENV['HAB_ORIGIN'] || habitat_cli_config['origin']
end

def initialize(path, options = {})

def initialize(path, options = {})
  @path    = path
  @options = options
  @cli_config = nil
  log_level = options.fetch('log_level', 'info')
  Habitat::Log.level(log_level.to_sym)
end

def output_dir

def output_dir
  options[:output_dir] || Dir.pwd
end

def package_name

def package_name
  "inspec-profile-#{profile.name}"
end

def plan_contents

def plan_contents
  plan = <<~EOL
    pkg_name=#{package_name}
    pkg_version=#{profile.version}
    pkg_origin=#{habitat_origin}
    pkg_deps=(chef/inspec core/ruby core/hab)
    pkg_svc_user=root
  EOL
  plan += "pkg_license='#{profile.metadata.params[:license]}'\n\n" if profile.metadata.params[:license]
  plan += <<~EOL
    do_build() {
      cp -vr $PLAN_CONTEXT/../* $HAB_CACHE_SRC_PATH/$pkg_dirname
    }
    do_install() {
      local profile_contents
      local excludes
      profile_contents=($(ls))
      excludes=(habitat results *.hart)
      for item in ${excludes[@]}; do
        profile_contents=(${profile_contents[@]/$item/})
      done
      mkdir ${pkg_prefix}/dist
      cp -r ${profile_contents[@]} ${pkg_prefix}/dist/
    }
  EOL
  plan
end

def run_hook_contents

def run_hook_contents
  <<~EOL
    #!/bin/sh
    # redirect stderr to stdout
    # ultimately, we'd like to log this somewhere useful, but due to
    # https://github.com/habitat-sh/habitat/issues/2395, we need to
    # avoid doing that for now.
    exec 2>&1
    # InSpec will try to create a .cache directory in the user's home directory
    # so this needs to be someplace writeable by the hab user
    export HOME={{pkg.svc_var_path}}
    PROFILE_IDENT="{{pkg.origin}}/{{pkg.name}}"
    RESULTS_DIR="{{pkg.svc_var_path}}/inspec_results"
    RESULTS_FILE="${RESULTS_DIR}/{{pkg.name}}.json"
    # Create a directory for inspec formatter output
    mkdir -p {{pkg.svc_var_path}}/inspec_results
    while true; do
      echo "Executing InSpec for ${PROFILE_IDENT}"
      inspec exec {{pkg.path}}/dist --format=json > ${RESULTS_FILE}
      if [ $? -eq 0 ]; then
        echo "InSpec run completed successfully."
      else
        echo "InSpec run did not complete successfully. If you do not see any errors above,"
        echo "control failures were detected. Check the InSpec results here for details:"
        echo ${RESULTS_FILE}
        echo "Otherwise, troubleshoot any errors shown above."
      fi
      source {{pkg.svc_config_path}}/settings.sh
      echo "sleeping for ${SLEEP_TIME} seconds"
      sleep ${SLEEP_TIME}
    done
  EOL
end

def setup

def setup
  Habitat::Log.info("Setting up profile at #{path} for Habitat...")
  create_profile_object
  verify_profile
  vendor_profile_dependencies
  create_habitat_directories(path)
  create_plan(path)
  create_run_hook(path)
  create_settings_file(path)
  create_default_config(path)
end

def upload

def upload
  validate_habitat_auth_token
  hart_file = create
  upload_hart(hart_file)
rescue => e
  Habitat::Log.debug(e.backtrace.join("\n"))
  exit_with_error(
    'Unable to upload Habitat artifact.',
    "#{e.class} -- #{e.message}",
  )
end

def upload_hart(hart_file)

def upload_hart(hart_file)
  Habitat::Log.info('Uploading the Habitat artifact to our Depot...')
  env = {
    'TERM'               => 'vt100',
    'HAB_AUTH_TOKEN'     => habitat_auth_token,
    'HAB_NONINTERACTIVE' => 'true',
  }
  env['HAB_DEPOT_URL'] = ENV['HAB_DEPOT_URL'] if ENV['HAB_DEPOT_URL']
  cmd = Mixlib::ShellOut.new("hab pkg upload #{hart_file}", env: env)
  cmd.run_command
  if cmd.error?
    exit_with_error(
      'Unable to upload Habitat artifact to the Depot.',
      cmd.stdout,
      cmd.stderr,
    )
  end
  Habitat::Log.info('Upload complete!')
end

def validate_habitat_auth_token

def validate_habitat_auth_token
  exit_with_error(
    'Unable to determine Habitat auth token for publishing.',
    'Run `hab setup` or set the HAB_AUTH_TOKEN environment variable.',
  ) if habitat_auth_token.nil?
end

def validate_habitat_installed

def validate_habitat_installed
  Habitat::Log.info('Checking to see if Habitat is installed...')
  cmd = Mixlib::ShellOut.new('hab --version')
  cmd.run_command
  exit_with_error('Unable to run Habitat commands.', cmd.stderr) if cmd.error?
end

def validate_habitat_origin

def validate_habitat_origin
  exit_with_error(
    'Unable to determine Habitat origin name.',
    'Run `hab setup` or set the HAB_ORIGIN environment variable.',
  ) if habitat_origin.nil?
end

def validate_output_dir

def validate_output_dir
  exit_with_error("Output directory #{output_dir} is not a directory or does not exist.") unless
    File.directory?(output_dir)
end

def vendor_profile_dependencies

def vendor_profile_dependencies
  profile_vendor = Inspec::ProfileVendor.new(path)
  if profile_vendor.lockfile.exist? && profile_vendor.cache_path.exist?
    Habitat::Log.info("Profile's dependencies are already vendored, skipping vendor process.")
  else
    Habitat::Log.info("Vendoring the profile's dependencies...")
    profile_vendor.vendor!
    Habitat::Log.info('Ensuring all vendored content has read permissions...')
    profile_vendor.make_readable
    # refresh the profile object since the profile now has new files
    create_profile_object
  end
end

def verify_profile

def verify_profile
  Habitat::Log.info('Checking to see if the profile is valid...')
  unless profile.check[:summary][:valid]
    exit_with_error('Profile check failed. Please fix the profile before creating a Habitat artifact.')
  end
  Habitat::Log.info('Profile is valid.')
end

def work_dir

def work_dir
  return @work_dir if @work_dir
  @work_dir ||= Dir.mktmpdir('inspec-habitat-exporter')
  Habitat::Log.debug("Generated work directory #{@work_dir}")
  @work_dir
end