# frozen_string_literal: true
#
# Author:: Tim Smith <tsmith@chef.io>
# Author:: Joshua Colson <joshua.colson@gmail.com>
# Copyright:: Copyright (c) Chef Software Inc.
# Copyright:: 2019 Joshua Colson
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#

Ohai.plugin(:Virtualbox) do
  depends "virtualization"
  provides "virtualbox"

  # query virtualbox for each configured vm, as well as
  # each guest's individual configuration settings
  def vboxmanage_list_vms
    vms = Mash.new
    so_cmd = "VBoxManage list --sorted vms"
    logger.trace(so_cmd)
    so = shell_out(so_cmd)

    if so.exitstatus == 0
      # parse the output
      so.stdout.lines.each do |line|
        case line
        when /^"(\S*)" \{(\S*)\}$/
          name = Regexp.last_match(1)
          uuid = Regexp.last_match(2)
          vms[name] = vboxmanage_vminfo(uuid)
        end
      end
    end
    vms
  rescue Ohai::Exceptions::Exec
    logger.trace("Plugin VboxHost: Could not run 'VBoxManage list --sorted vms'. Skipping data")
  end

  # query the vminfo for particular guest instance, normalizing
  # the fields so that they're not enclosed in double-quotes (")
  def vboxmanage_vminfo(machine_id)
    vm = Mash.new

    so_cmd = "VBoxManage showvminfo #{machine_id} --machinereadable"
    logger.trace(so_cmd)
    so = shell_out(so_cmd)

    if so.exitstatus == 0
      so.stdout.lines.each do |line|
        line.chomp!
        left, right = line.split("=")

        # remove enclosing quotes, if needed
        key = left.delete_prefix('"').delete_suffix('"')

        # skip the name attribute since that is the parent key
        next if key == "name"

        vm[key.downcase] = right.delete_prefix('"').delete_suffix('"')
      end
    end
    vm
  rescue Ohai::Exceptions::Exec
    logger.trace("Plugin VboxHost: Could not run '#{so_cmd}'. Skipping data")
  end

  # query virtualbox for a list of #{query_type} items
  # these queries return a result set that is delimited by
  # multiple successive newlines, with each block containing
  # key/value pairs delimited by a colon (:) and column aligned
  #
  # the keys of each k/v pair are normalized to lowercase
  def vboxmanage_list_blocks(query_type, name_key)
    # ignore unrecognized query type
    supported_queries = %w{
      bridgedifs dhcpservers dvds hdds hostdvds
      hostfloppies hostonlyifs natnets
    }
    return nil unless supported_queries.include? query_type

    results = Mash.new

    so_cmd = "VBoxManage list --sorted #{query_type}"
    logger.trace(so_cmd)
    so = shell_out(so_cmd)
    # raise an exception if the command fails
    # so.error!

    if so.exitstatus == 0
      # break the result into paragraph blocks, on successive newlines
      so.stdout.each_line("") do |blk|
        # remove the multiple newlines of each record
        blk.chomp!.chomp!
        # initialize a blank record hash
        record = Mash.new
        # parse the record block into key/value pairs
        blk.each_line do |line|
          next unless line.include? ":"

          # split the line into key/value pair
          key, right = line.split(":", 2)

          # strip the leading/trailing whitespace if the value is not nil
          value = right.nil? ? "" : right.strip
          record[key.downcase] = value
        end

        # insert the record into the list of results
        if record.key? name_key.downcase
          name = record.delete(name_key.downcase)
          results[name] = record
        end
      end
    end
    results
  rescue Ohai::Exceptions::Exec
    logger.trace("Plugin VboxHost: Could not run '#{so_cmd}'. Skipping data")
  end

  collect_data(:windows, :linux, :solaris2, :darwin) do
    case virtualization.dig("systems", "vbox")
    when "guest"
      logger.trace("Plugin Virtualbox: Node detected as vbox guest. Collecting guest data.")
      begin
        so = shell_out("VBoxControl guestproperty enumerate")

        if so.exitstatus == 0
          virtualbox Mash.new
          virtualbox[:host] = Mash.new
          virtualbox[:guest] = Mash.new
          so.stdout.lines.each do |line|
            case line
            when /LanguageID, value: (\S*),/
              virtualbox[:host][:language] = Regexp.last_match(1)
            when /VBoxVer, value: (\S*),/
              virtualbox[:host][:version] = Regexp.last_match(1)
            when /VBoxRev, value: (\S*),/
              virtualbox[:host][:revision] = Regexp.last_match(1)
            when %r{GuestAdd/VersionExt, value: (\S*),}
              virtualbox[:guest][:guest_additions_version] = Regexp.last_match(1)
            when %r{GuestAdd/Revision, value: (\S*),}
              virtualbox[:guest][:guest_additions_revision] = Regexp.last_match(1)
            end
          end
        end
      rescue Ohai::Exceptions::Exec
        logger.trace('Plugin Virtualbox: Could not execute "VBoxControl guestproperty enumerate". Skipping data collection.')
      end
    when "host"
      logger.trace("Plugin Virtualbox: Node detected as vbox host. Collecting host data.")
      virtualbox Mash.new
      begin
        # get a list of virtualbox guest vms
        virtualbox["guests"] = vboxmanage_list_vms

        # get a list of virtualbox virtual hard disk drives
        virtualbox["hdds"] = vboxmanage_list_blocks("hdds", "Location")

        # get a list of virtualbox virtual dvd drives
        virtualbox["dvds"] = vboxmanage_list_blocks("dvds", "Location")

        # get a list of virtualbox host dvd drives
        virtualbox["hostdvds"] = vboxmanage_list_blocks("hostdvds", "Name")

        # get a list of virtualbox host floppy drives
        virtualbox["hostfloppies"] = vboxmanage_list_blocks("hostfloppies", "Name")

        # get a list of virtualbox hostonly network interfaces
        virtualbox["hostonlyifs"] = vboxmanage_list_blocks("hostonlyifs", "Name")

        # get a list of virtualbox bridged network interfaces
        virtualbox["bridgedifs"] = vboxmanage_list_blocks("bridgedifs", "Name")

        # get a list of virtualbox dhcp servers
        virtualbox["dhcpservers"] = vboxmanage_list_blocks("dhcpservers", "NetworkName")

        # get a list of virtualbox nat networks
        virtualbox["natnets"] = vboxmanage_list_blocks("natnets", "NetworkName")
      rescue Ohai::Exceptions::Exec
        logger.trace("Plugin VboxHost: Could not collect data for VirtualBox host. Skipping data")
      end
    else
      logger.trace("Plugin Virtualbox: Not on a Virtualbox host or guest. Skipping plugin.")
    end
  end
end
