#!/usr/bin/env ruby

# -------------------------------------------------------------------------- #
# Copyright 2002-2025, OpenNebula Project, OpenNebula Systems                #
#                                                                            #
# 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.                                             #
#--------------------------------------------------------------------------- #

ONE_LOCATION = ENV['ONE_LOCATION']

if !ONE_LOCATION
    RUBY_LIB_LOCATION = '/usr/lib/one/ruby'
    GEMS_LOCATION     = '/usr/share/one/gems'
else
    RUBY_LIB_LOCATION = ONE_LOCATION + '/lib/ruby'
    GEMS_LOCATION     = ONE_LOCATION + '/share/gems'
end

# %%RUBYGEMS_SETUP_BEGIN%%
require 'load_opennebula_paths'
# %%RUBYGEMS_SETUP_END%%

$LOAD_PATH << RUBY_LIB_LOCATION
$LOAD_PATH << RUBY_LIB_LOCATION + '/cli'

require 'command_parser'
require 'one_helper/onevrouter_helper'
require 'one_helper/onetemplate_helper'
require 'one_helper/onevm_helper'

CommandParser::CmdParser.new(ARGV) do
    usage '`onevrouter` <command> [<args>] [<options>]'
    version OpenNebulaHelper::ONE_VERSION

    helper = OneVirtualRouterHelper.new

    before_proc do
        helper.set_client(options)
    end

    USE = {
        :name => 'use',
        :large => '--use',
        :description => 'lock use actions'
    }

    MANAGE = {
        :name => 'manage',
        :large => '--manage',
        :description => 'lock manage actions'
    }

    ADMIN = {
        :name => 'admin',
        :large => '--admin',
        :description => 'lock admin actions'
    }

    ALL = {
        :name => 'all',
        :large => '--all',
        :description => 'lock all actions'
    }

    ########################################################################
    # Global Options
    ########################################################################
    set :option, CommandParser::OPTIONS + OpenNebulaHelper::CLIENT_OPTIONS

    list_options  = CLIHelper::OPTIONS
    list_options += OpenNebulaHelper::FORMAT
    list_options << OpenNebulaHelper::NUMERIC
    list_options << OpenNebulaHelper::DESCRIBE

    ########################################################################
    # Formatters for arguments
    ########################################################################
    set :format, :groupid, OpenNebulaHelper.rname_to_id_desc('GROUP') do |arg|
        OpenNebulaHelper.rname_to_id(arg, 'GROUP')
    end

    set :format, :userid, OpenNebulaHelper.rname_to_id_desc('USER') do |arg|
        OpenNebulaHelper.rname_to_id(arg, 'USER')
    end

    set :format, :vrouterid, OneVirtualRouterHelper.to_id_desc do |arg|
        helper.to_id(arg)
    end

    set :format, :vrouterid_list,
        OneVirtualRouterHelper.list_to_id_desc do |arg|
        helper.list_to_id(arg)
    end

    set :format, :filterflag,
        OneVirtualRouterHelper.filterflag_to_i_desc do |arg|
        helper.filterflag_to_i(arg)
    end

    set :format, :templateid,
        OpenNebulaHelper.rname_to_id_desc('VMTEMPLATE') do |arg|
        OpenNebulaHelper.rname_to_id(arg, 'VMTEMPLATE')
    end

    set :format, :nicid, 'NIC id' do |arg|
        format_int(arg)
    end

    ########################################################################
    # Commands
    ########################################################################

    create_desc = <<-EOT.unindent
        Creates a new Virtual Router from the given description
        #{OpenNebulaHelper::TEMPLATE_INPUT}
    EOT

    command :create, create_desc, [:file, nil] do
        helper.create_resource(options) do |obj|
            begin
                if args[0]
                    template = File.read(args[0])
                elsif !(stdin = OpenNebulaHelper.read_stdin).empty?
                    template = stdin
                end
                obj.allocate(template)
            rescue StandardError => e
                STDERR.puts e.message
                exit(-1)
            end
        end
    end

    instantiate_desc = <<-EOT.unindent
        Creates a new VM instance from the given Template. This VM can be
        managed with the 'onevm' command.

        #{OpenNebulaHelper::TEMPLATE_INPUT}

        The NIC elements defined in the Virtual Router will be used. The
        source Template can be modified adding or replacing attributes with
        the optional file argument, or with the options.
    EOT

    instantiate_options = [
        OneTemplateHelper::VM_NAME,
        OneTemplateHelper::MULTIPLE,
        OneVMHelper::HOLD
    ]

    command :instantiate, instantiate_desc,
            :vrouterid, :templateid, [:file, nil],
            :options => instantiate_options +
                        OpenNebulaHelper::TEMPLATE_OPTIONS do
        conflicting_opts = []
        if (args[2] || !(stdin = OpenNebulaHelper.read_stdin).empty?) &&
            OpenNebulaHelper.create_template_options_used?(options, conflicting_opts)

            STDERR.puts 'You cannot pass template on STDIN and use template creation options, ' <<
                        "conflicting options: #{conflicting_opts.join(', ')}."
            exit(-1)
        end

        number = options[:multiple] || 1
        user_inputs = options[:user_inputs]

        helper.perform_action(args[0], options, 'instantiated') do |vr|
            name = options[:name] || ''

            t = OpenNebula::Template.new_with_id(args[1], helper.client)

            on_hold = !options[:hold].nil?

            extra_template = ''
            rc = t.info

            if OpenNebula.is_error?(rc)
                STDERR.puts rc.message
                exit(-1)
            end

            if args[2]
                extra_template = File.read(args[2])
            elsif !stdin.empty?
                extra_template = stdin
            else
                res = OpenNebulaHelper.create_template(options, t)

                if res.first != 0
                    STDERR.puts res.last
                    next -1
                end

                extra_template = res.last
            end

            if !user_inputs
                user_inputs = OneTemplateHelper.get_user_inputs(t.to_hash)
            else
                optionals = OneTemplateHelper.get_user_inputs(
                    t.to_hash,
                    options[:user_inputs_keys]
                )

                user_inputs = user_inputs + "\n" + optionals
            end

            extra_template << "\n" << user_inputs

            vr.instantiate(number, args[1], name, on_hold, extra_template)
        end
    end

    delete_desc = <<-EOT.unindent
        Deletes the given Virtual Router
    EOT

    command :delete, delete_desc, [:range, :vrouterid_list] do
        helper.perform_actions(args[0], options, 'deleted') do |obj|
            obj.delete
        end
    end

    chgrp_desc = <<-EOT.unindent
        Changes the Virtual Router group
    EOT

    command :chgrp, chgrp_desc, [:range, :vrouterid_list], :groupid do
        helper.perform_actions(args[0], options, 'Group changed') do |obj|
            obj.chown(-1, args[1].to_i)
        end
    end

    chown_desc = <<-EOT.unindent
        Changes the Virtual Router owner and group
    EOT

    command :chown, chown_desc, [:range, :vrouterid_list], :userid,
            [:groupid, nil] do
        args[2].nil? ? gid = -1 : gid = args[2].to_i
        helper.perform_actions(args[0], options, 'Owner/Group changed') do |obj|
            obj.chown(args[1].to_i, gid)
        end
    end

    chmod_desc = <<-EOT.unindent
        Changes the Virtual Router permissions
    EOT

    command :chmod, chmod_desc, [:range, :vrouterid_list], :octet do
        helper.perform_actions(args[0], options, 'Permissions changed') do |obj|
            obj.chmod_octet(OpenNebulaHelper.to_octet(args[1]))
        end
    end

    update_desc = <<-EOT.unindent
        Update the Virtual Router contents. If a path is not provided the editor
        will be launched to modify the current content.
    EOT

    command :update, update_desc, :vrouterid, [:file, nil],
            :options => OpenNebulaHelper::APPEND do
        helper.perform_action(args[0], options, 'modified') do |obj|
            if options[:append]
                str = OpenNebulaHelper.append_template(args[0], obj, args[1])
            else
                str = OpenNebulaHelper.update_template(args[0], obj, args[1])
            end

            obj.update(str, options[:append])
        end
    end

    rename_desc = <<-EOT.unindent
        Renames the Virtual Router
    EOT

    command :rename, rename_desc, :vrouterid, :name do
        helper.perform_action(args[0], options, 'renamed') do |obj|
            obj.rename(args[1])
        end
    end

    nic_attach_desc = <<-EOT.unindent
        Attaches a NIC to a VirtualRouter, and each one of its VMs. When using
        --file add only one NIC instance.

        Note if you are using a HA configuration for this router, --float can be
        added. When an IP is requested in HA clusters use --float.
    EOT

    command :"nic-attach", nic_attach_desc, :vrouterid,
            :options => [OpenNebulaHelper::FILE,
                         OneVMHelper::NETWORK,
                         OneVMHelper::IP,
                         OneVirtualRouterHelper::FLOAT] do
        if options[:file]
            template = File.read(options[:file])
        elsif !(stdin = OpenNebulaHelper.read_stdin).empty?
            template = stdin
        elsif options[:network]
            network_id = options[:network]
            ip         = options[:ip]
            float      = options[:float]

            template = "NIC = [ NETWORK_ID = #{network_id}"

            if ip
                template += ", IP = #{ip}"
            end

            if float
                template += ', FLOATING_IP ="yes"'
            end

            template += ' ]'
        else
            STDERR.puts 'Provide a template or a network:'
            STDERR.puts "\t--file    <file>"
            STDERR.puts "\t--network <network>"
            exit(-1)
        end

        helper.perform_action(args[0], options, 'Attach NIC') do |vr|
            vr.nic_attach(template)
        end
    end

    nic_detach_desc = <<-EOT.unindent
        Detaches a NIC from a VirtualRouter, and each one of its VMs
    EOT

    command :"nic-detach", nic_detach_desc, :vrouterid, :nicid do
        nicid = args[1].to_i

        helper.perform_action(args[0], options, 'Detach NIC') do |vr|
            vr.nic_detach(nicid)
        end
    end

    list_desc = <<-EOT.unindent
        Lists the Virtual Routers in the pool. #{OneVirtualRouterHelper.list_layout_help}
    EOT

    command :list, list_desc, [:filterflag, nil], :options => list_options do
        helper.list_pool(options, false, args[0])
    end

    show_desc = <<-EOT.unindent
        Shows information for the given Virtual Router
    EOT

    command :show, show_desc, :vrouterid,
            :options => [OpenNebulaHelper::FORMAT, OneVMHelper::ALL_TEMPLATE] do
        helper.show_resource(args[0], options)
    end

    top_desc = <<-EOT.unindent
        Lists Virtual Routers continuously
    EOT

    command :top, top_desc, [:filterflag, nil], :options => list_options do
        helper.list_pool(options, true, args[0])
    end

    lock_desc = <<-EOT.unindent
        Locks a Virtual Router to prevent certain actions defined by different levels.
        The show action will never be locked.
        Valid states are: All.
        Levels:
        [Use]: locks Admin, Manage and Use actions.
        [Manage]: locks Manage and Use actions.
        [Admin]: locks only Admin actions.
    EOT

    command :lock, lock_desc, [:range, :vrouterid_list],
            :options => [USE, MANAGE, ADMIN, ALL] do
        helper.perform_actions(args[0], options, 'VRouter locked') do |vr|
            if !options[:use].nil?
                level = 1
            elsif !options[:manage].nil?
                level = 2
            elsif !options[:admin].nil?
                level = 3
            elsif !options[:all].nil?
                level = 4
            else
                level = 1
            end
            vr.lock(level)
        end
    end

    unlock_desc = <<-EOT.unindent
        Unlocks a Virtual Router.
        valid states are: All.
    EOT

    command :unlock, unlock_desc, [:range, :vrouterid_list] do
        helper.perform_actions(args[0], options, 'VRouter unlocked') do |vr|
            vr.unlock
        end
    end
end
