# frozen_string_literal: true

# Copyright:: 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance
# with the License. A copy of the License is located at http://aws.amazon.com/apache2.0/
# or in the "LICENSE.txt" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES
# OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions and
# limitations under the License.

provides :manage_ebs
unified_mode true

property :shared_dir_array, Array, required: %i(mount export unmount unexport)
property :vol_array, Array, required: %i(mount unmount)
property :mode, String, default: "1777"

default_action :mount

action :mount do
  return if on_docker?
  shared_dir_array = new_resource.shared_dir_array.dup
  vol_array = new_resource.vol_array.dup

  # Mount each volume
  dev_path = [] # device labels

  vol_array.each_with_index do |volumeid, index|
    dev_path[index] = "/dev/disk/by-ebs-volumeid/#{volumeid}"

    volume "attach volume #{index}" do
      volume_id volumeid
      action :attach
    end

    # Setup disk, will be formatted xfs if empty
    ruby_block "setup_disk_#{index}" do
      block do
        dev_path[index] = prepare_disk(dev_path[index])
      end
      action :nothing
      subscribes :run, "volume[attach volume #{index}]", :immediately
    end

    volume "mount volume #{index}" do
      action :mount
      mode new_resource.mode
      shared_dir shared_dir_array[index]
      device(lazy_uuid(dev_path[index]))
      fstype(DelayedEvaluator.new { node['cluster']['volume_fs_type'] })
      device_type :uuid
      options "_netdev"
      retries 10
      retry_delay 6
    end
  end
end

action :export do
  return if on_docker?
  new_resource.shared_dir_array.dup.each do |dir|
    volume "export volume #{dir}" do
      shared_dir dir
      action :export
    end
  end
end

action :unmount do
  return if on_docker?
  new_resource.vol_array.each_with_index do |volumeid, index|
    volume "unmount volume #{index}" do
      shared_dir new_resource.shared_dir_array[index]
      action :unmount
    end

    volume "detach volume #{index}" do
      volume_id volumeid
      action :detach
    end
  end
end

action :unexport do
  return if on_docker?
  new_resource.shared_dir_array.dup.each do |dir|
    volume "unexport volume #{dir}" do
      shared_dir dir
      action :unexport
    end
  end
end

def lazy_uuid(device)
  DelayedEvaluator.new { get_uuid(device) }
end

#
# Gets the uuid of a device
#
def get_uuid(device)
  Chef::Log.info("Getting uuid for device: #{device}")
  match = shell_out("blkid -c /dev/null #{device}").stdout.match(/\sUUID="(.*?)"/)
  match = '' if match.nil?
  Chef::Log.info("uuid for device: #{device} is #{match[1]}")
  match[1]
end

#
# Checks if device is partitioned; if yes returns pt type
#
def get_pt_type(device)
  match = shell_out("blkid -c /dev/null #{device}").stdout.match(/\sPTTYPE="(.*?)"/)
  match = '' if match.nil?

  Chef::Log.info("Partition type for device #{device}: #{match[1]}")
  match[1]
end

#
# Check if block device has a filesystem
#
def get_fs_type(device)
  match = shell_out("blkid -c /dev/null #{device}").stdout.match(/\sTYPE="(.*?)"/)
  match = '' if match.nil?

  Chef::Log.info("File system type for device #{device}: #{match[1]}")
  match[1]
end

#
# Format a block device using the EXT4 file system if it is not already
# formatted.
#
def setup_disk(path)
  dev = ::File.readlink(path)
  full_path = ::File.absolute_path(dev, ::File.dirname(path))

  fs_type = get_fs_type(full_path)
  if fs_type.nil?
    shell_out("mkfs.ext4 #{full_path}")
    fs_type = 'ext4'
  end

  fs_type
end

#
# Returns the first partition of a device, provided via sym link
#
def get_1st_partition(device)
  # Resolves the real device name (ex. /dev/sdg)
  Chef::Log.info("Getting 1st partition for device: #{device}")
  partition = "/dev/#{shell_out("lsblk -ln -o Name #{device}|awk 'NR==2'").stdout.strip}"
  Chef::Log.info("1st partition for device: #{device} is: #{partition}")
  partition
end

def prepare_disk(device_path)
  pt_type = get_pt_type(device_path)
  if pt_type.nil?
    Chef::Log.info("device #{device_path} not partitioned, mounting directly")
    fs_type = setup_disk(device_path)
  else
    # Partitioned device, mount 1st partition
    Chef::Log.info("device #{device_path} partitioned, mounting first partition")
    partition_dev = get_1st_partition(device_path)
    Chef::Log.info("First partition for device #{device_path} is: #{partition_dev}")
    fs_type = get_fs_type(partition_dev)
    device_path = partition_dev
  end
  node.default['cluster']['volume_fs_type'] = fs_type
  device_path
end
