#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# If the python sdk is not installed, run 'pip install alibabacloud-ecs20140526'.
# Make sure you are using the latest sdk version. Run 'pip install --upgrade alibabacloud-ecs20140526' to upgrade.

import sys
import json
import logging
import os
import time
import datetime
import base64

from alibabacloud_credentials.client import Client as CredentialClient
from alibabacloud_credentials.models import Config as CredentialConfig
from alibabacloud_ecs20140526.client import Client as EcsClient
from alibabacloud_ecs20140526 import models as ecs_models
from alibabacloud_tea_openapi import models as open_api_models
from alibabacloud_tea_util import models as util_models
from alibabacloud_tea_util.client import Client as UtilClient

# configuration the log output formatter, if you want to save the output to file,
# append ",filename='ecs_invoke.log'" after datefmt.
logging.basicConfig(level=logging.INFO,
                    format='%(asctime)s %(filename)s[line:%(lineno)d] %(levelname)s %(message)s',
                    datefmt='%a, %d %b %Y %H:%M:%S', filename='dhcpclientfix.log', filemode='w')

# The ak, region will accept from user input.
access_key = ''
access_key_secret = ''
region_name = ''
clt = None

windows_dhcp_script_str = """
$fix = $true
[datetime]$expireDate = New-Object DateTime
$ShouldRenew = $false
$ExpireInDays = 500
$unknowerror = $false
Get-WmiObject -Class "Win32_NetworkAdapterConfiguration" -Filter "IPenabled = 'true' and DHCPEnabled = 'true' and ServiceName = 'netkvm'" | % { $dhcpexpireStr = ($_.DHCPLeaseExpires).Substring(0, 8)
    if ([DateTime]::TryParseExact($dhcpexpireStr, "yyyyMMdd",
            [System.Globalization.CultureInfo]::InvariantCulture,
            [System.Globalization.DateTimeStyles]::None,
            [ref]$expireDate)) {
        $leasedays = ($expireDate - [DateTime]::Today).TotalDays
        if ($leasedays -le $ExpireInDays) {
            Write-Host "Nic info: $($_.IPAddress), $($_.Description), $($_.ServiceName), $($_.Index), $($_.DHCPLeaseExpires) leasedays: ${leasedays}"
            $ShouldRenew = $true
        }
    }
    else {
        $unknowerror = $true
    }
}

if ($ShouldRenew) {
    Write-Host "Found one ip will expire in ${ExpireInDays} days. We need fixing it!!!"
    if ($fix) {
        Write-Host "Fix it now..."
        & ipconfig /renew | Out-Null
        if ($LastExitCode -eq 0) {

            # double check
            # ignore 1709 as this is a bug.
            $IsCore1709 = $false
            $OperationSystem = Get-WmiObject -Class Win32_OperatingSystem
            $OSVer = [version]$OperationSystem.version
            if ($OSVer.Major -ge 10) {
                $ReleaseID = Get-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion" -Name "ReleaseId"
                if ($ReleaseID.ReleaseId -eq "1709") {
                    $IsCore1709 = $true
                }
            }
            $CheckAgain = $false
            Get-WmiObject -Class "Win32_NetworkAdapterConfiguration" -Filter "IPenabled = 'true' and DHCPEnabled = 'true' and ServiceName = 'netkvm'" | % { $dhcpexpireStr = ($_.DHCPLeaseExpires).Substring(0, 8)
                if ([DateTime]::TryParseExact($dhcpexpireStr, "yyyyMMdd",
                        [System.Globalization.CultureInfo]::InvariantCulture,
                        [System.Globalization.DateTimeStyles]::None,
                        [ref]$expireDate)) {
                    $leasedays = ($expireDate - [DateTime]::Today).TotalDays
                    if ($leasedays -le $ExpireInDays) {
                        Write-Host "Nic info: $($_.IPAddress), $($_.Description), $($_.ServiceName), $($_.Index), $($_.DHCPLeaseExpires) leasedays: ${leasedays}"
                        $CheckAgain = $true
                    }
                }
            }
            if ($CheckAgain -and (-not $IsCore1709)) {
                Write-Host "Error: renew command executed but lease time did not refresh."
                exit 5
            }
            else {
                Write-Host "fix success."
                exit 0
            }
        }
        else {
            Write-Host "Renew ip failed with error: ${LastExitCode}."
            exit 5
        }
    }
}
else {
    if ($unknowerror) {
        Write-Host "Failed to check the expire time of the ip address, please contact support."
        exit 2
    }
    Write-Host "No ip will expire in recent ${ExpireInDays} days. Then no need fix."
    exit 0
}
"""
linux_dhcp_script_str = """
#!/bin/bash
datetime=$(date +%Y%m%d-%H:%M:%S)
log_file=/var/log/dhcp_fix_${datetime}.log
do_fix=false
lft_threshold=252288000 # 8 years secs

function do_log() {
    local color=$1
    local level=$2
    shift
    shift
    local msg=$@
    local prefix=""

    if [[ $color -gt 30 ]]; then
        prefix="\033[1;$color;40m[$level]\033[0m"
    else
        prefix="[$level]"
    fi
	echo -e "$prefix [`date +'%Y-%m-%d %H:%M:%S'`] $msg" | tee -a $log_file
}

function log_info() {
    local msg=$@
    do_log 0 INFO $msg
}

function log_warn() {
    local msg=$@
    do_log 33 WARN $msg
}

function log_error() {
    local msg=$@
    do_log 31 ERROR $msg
}

function log_success() {
    local msg=$@
    do_log 32 SUCCESS $msg
}

function check_dhclient() {
    # dhclient is running -> 0
    # otherwise -> 1
    local interface=$1
    if   ps aux | grep -v grep | grep -q "dhclient -v" ; then # by dhclient -v
        return 0
    elif ps aux | grep -v grep | grep -q "dhclient $interface\$" ; then # by dhclient ethX
        return 0
    elif ps aux | grep -v grep | grep '/sbin/dhclient' | grep -q "$interface\$" ; then # by ifup ethX
        return 0
    fi
    return 1
}

function check_interface() {
    # don't need fix -> 0
    # need fix -> 1
    local interface=$1
    log_info "start checking $interface"

    local if_state=`cat /sys/class/net/$interface/operstate`
    log_info "$interface state is $if_state"

    if [[ $if_state == down ]]; then
        log_info "no need fix, $interface is link down"
        return 0
    fi

    log_info "ip a show dev $interface"
    ip a show dev $interface >> $log_file

    local valid_lft=$(ip a show dev $interface | awk '/valid_lft/ {print $2}')
    valid_lft=${valid_lft%sec}
    log_info "$interface valid_lft $valid_lft"

    if [[ -z $valid_lft ]]; then
        log_warn "$interface valid_lft is empty, skip it"
        return 0
    fi

    # interface is link up and the valid_lft of `ip a show dev $interface` could be:
    # if configured with dhcp -> valid_lft ${num}sec
    # if configured with static -> valid_lft forever

    if [[ $valid_lft == forever ]]; then
        log_info "no need fix, $interface is configured statically"
        return 0
    fi

    if check_dhclient $interface ; then
        log_info "no need fix, dhclient $interface is running"
        return 0
    else
        log_warn "dhclient $interface not running"
    fi

    log_warn "need fixing $interface"
    return 1
}

function fix_interface() {
    # fixed ok -> 0
    # fixed error -> 1
    local interface=$1
    log_info "start fixing $interface"
    log_info "ip a show dev $interface"
    ip a show dev $interface >> $log_file

    local ifcfg=/etc/sysconfig/network-scripts/ifcfg-$interface

    if [[ ! -f $ifcfg ]]; then
        log_warn "not find $ifcfg, please fixing manually"
        return 1
    fi

    if [[ $interface != eth0 ]] && ! grep -q 'DEFROUTE=no' $ifcfg ; then
        log_warn "$interface is set as linked to default route, please fixing manually"
        return 1
    fi

    log_info "$ifcfg content"
    cat $ifcfg >> $log_file

    if ! grep -q "BOOTPROTO=dhcp" $ifcfg ; then
        log_warn "$ifcfg not configured dhcp, skip"
        return 0
    fi

    log_info "ifup $interface"
    /usr/sbin/ifup $interface >> $log_file

    log_info "ip a show dev $interface"
    ip a show dev $interface >> $log_file

    if check_dhclient $interface ; then
        log_info "ok, dhclient $interface is running now"
        return 0
    else
        log_warn "dhclient $interface still not running !"
        return 1
    fi
}

function fix_net_hotplug() {
    base64_path=`command -v base64`
    gzip_path=`command -v gzip`

    if [[ -z $base64_path ]]; then
        log_warn "not find base64 command"
        return 1
    elif [[ -z $gzip_path ]]; then
        log_warn "not find gzip command"
        return 1
    fi

    net_hotplug_encoding="H4sIAKR0xFwAA7VV30/bMBB+rv+KI1RsdCsZr0N9QAwG0lTQ1D0hFLnJJbEItmU7pdXK/75zfjQpa9E0iT60tu/77j7ffUkPD8K5kKHNGTu06GC8pAV8U/KDg0QBlyuXC5mBSMHlCBLdszKPICxYp7TGhFHkHg5gnEK44CYsVPwY2nJuVzZs0Q9nniwZ0AeXwsEXloqa6GkJLsITISOiCEnRB9iJZ1aVJkYI0cUhShGVThTWr8Y5FhoNYzfT2eXPq/OLy+jidnoV3Z3PricVnFLHSqYiazWNbWyEdpZ5GQfg0xUqg4DCJ7lyuiizr3Tq0EDvKHgt7LQSdgLD3ztKv2yKpaWMnVCyLncPwXADD2AygSDY9GiwW4rwUlJO16fWy7IoAkJ2CmJuEYbnF7Ob2ylhGU+StcFMWGIdMzao45uiHjIYHHZZLRXnDrhBmBtVZrmDUgO3wEFz40ClUPevNNxfhNha69Fa1N82kaO1LgStC0V7k/DROimfnlYeYjEerV1JEMf16Ji4g2aqtDo726ukvQAmVWt6yohS56k9RJ7QRsW+3eGi4DLsjeOl6+xWXepatdeKrnczja5vZ3c/fn2fnHZplxBa/3CIlHrRz0LdNZPhxwxdlD/7TX/+L5WwJoWkUXtAsMUfyLqZTY56E81XXTb/0yQaNGkaDmWCoyPYlJu0gQpc32qPh3jlwdYmn3ue6tnxDT4uMe635C/W3njXcD9utDxmfsUMPqkFrkvZefV/rJqoZ+nN2lo1wfc0a3v4T86ppG058P1HU9V8czivEPvG429av2TAb6sA2y2gQL7ArVdl84fyacn+AIuYGhVkBgAA"
    echo $net_hotplug_encoding | $base64_path -d | $gzip_path -d > /etc/eni_utils/net.hotplug
}

function work() {
    local interface=
    local ret=0
    for i in /sys/class/net/eth* ; do
        interface=${i#/sys/class/net/}
        if ! check_interface $interface ; then
            if $do_fix ; then
                if ! fix_interface $interface ; then
                    ret=1
                fi
            else
                ret=1
            fi
        fi
    done
    fix_net_hotplug
    return $ret
}

function check_eni_utils() {
    # centos7 with eni_utils
    if [[ -f /etc/eni_utils/net.hotplug && -d /var/run/systemd/ ]]; then
        return 0
    fi
    return 1
}

function main() {
    if [[ $1 == fix ]] ; then
        do_fix=true
        action="check and fix"
    else
        do_fix=false
        action="check"
    fi

    log_info "start $action"
    log_info "log file $log_file"

    if ! check_eni_utils ; then
        log_info "no need fix"
        log_success "$action success"
        log_success "return code: 0"
        return 0
    fi

    if work ; then
        log_success "$action success"
        log_success "return code: 0"
        return 0
    else
        log_error "$action failed"
        log_error "return code: 1"
        return 1
    fi
}

main fix""" # NOTE

# Get all instances that may affected with ostype info:
# criteria: VPC, Running, Windows, Linux(CentOS)
# return a dict contains all instances.
def get_allaffectedinstances():
    d_instancewithos = {}
    page_number = 1
    page_size = 100  # DescribeInstancesRequest support up to 100
    # Create request for DescribeInstances
    request = ecs_models.DescribeInstancesRequest(
        region_id=region_name,
        page_number=page_number,
        page_size=page_size
    )
    response = _send_request(request)
    if response == None:
        raise Exception("Failed to query instances of region %s, please check the region name, ak info." % region_name)
    integer_page = response.body.total_count // page_size
    total_page = integer_page if response.body.total_count % page_size == 0 else (integer_page + 1)

    while page_number <= total_page:
        instances = response.body.instances
        if instances == None:
            raise Exception(
                "Failed to query instances of region %s, please check the region name, ak info." % region_name)

        instance_list = instances.instance
        for i in instance_list:
            instanceid = i.instance_id
            ostypeinfo = i.ostype
            osname = i.osname
            networktype = i.instance_network_type
            status = i.status
            logging.info(
                'find one instance of region %s: InstanceID: %s, OSType: %s, OSName: %s, networktype: %s, status: %s',
                region_name, instanceid, ostypeinfo, osname, networktype, status)
            if networktype == 'vpc' and status == 'Running':  # Only VPC, running instance should be checked.
                if ostypeinfo == 'linux':  # Linux, only centos 7 was affected.
                    if osname.startswith("CentOS  7.2") or osname.startswith("CentOS  7.3") or osname.startswith(
                            "CentOS  7.4"):
                        d_instancewithos[instanceid] = ostypeinfo
                        logging.info('Add instance InstanceID: %s, OSType: %s to check list.', instanceid, ostypeinfo)
                else:
                    d_instancewithos[instanceid] = ostypeinfo
                    logging.info('Add instance InstanceID: %s, OSType: %s to check list.', instanceid, ostypeinfo)

        page_number += 1
        # Create request for DescribeInstances with updated page number
        request = ecs_models.DescribeInstancesRequest(
            region_id=region_name,
            page_number=page_number,
            page_size=page_size
        )
        response = _send_request(request)

    return d_instancewithos


# return tuples as install axt list and not install axt list.
def check_axtinstalled(instances_dict):
    installed_list = []
    notinstalled_list = []
    # DescribeCloudAssistantStatusRequest supports up to 50 instances.
    for inst_items in chunks(list(instances_dict.keys()), 50):
        # Create request for DescribeCloudAssistantStatus
        request = ecs_models.DescribeCloudAssistantStatusRequest()
        request.region_id = region_name
        request.instance_id = inst_items  # 修改为正确的参数名
        response = _send_request(request)

        if response is None:
            logging.info('query assistant agent status failed')
            return None
        status_set = response.body.instance_cloud_assistant_status_set
        if status_set:
            d = status_set.instance_cloud_assistant_status
            for item in d:
                instanceid = item.instance_id
                ostype_info = instances_dict[instanceid]
                if item.cloud_assistant_status == "true":
                    installed_list.append((instanceid, ostype_info))
                else:
                    notinstalled_list.append((instanceid, ostype_info))
        else:
            logging.info('query assistant agent status error')
            return None
    return (installed_list, notinstalled_list)


def create_command(command_content, type, name='', description=''):
    # Create request for CreateCommand
    request = ecs_models.CreateCommandRequest(
        command_content=command_content,
        type=type,
        name=name,
        description=description,
        region_id=region_name  # 添加缺失的region_id参数
    )
    response = _send_request(request)
    if response is None:
        return None
    return response.body.command_id


def invoke_command(instance_list, command_id, timed=False, cronat=''):
    # Create request for InvokeCommand
    # 修复参数问题，使用正确的参数名
    request = ecs_models.InvokeCommandRequest(
        instance_id=instance_list,  # 修改为正确的参数名
        command_id=command_id,
        timed=timed,
        frequency=cronat,
        region_id=region_name
    )
    response = _send_request(request)
    if response is None:
        return None
    return response.body.invoke_id


def get_task_output_by_id(invoke_id):
    result_list = []
    # Create request for DescribeInvocationResults
    request = ecs_models.DescribeInvocationResultsRequest(
        invoke_id=invoke_id,
        region_id=region_name
    )
    page_size = 50  # DescribeInvocationResultsRequest only support up to 50
    page_number = 1
    request.page_number = page_number
    request.page_size = page_size
    response = _send_request(request)
    if response is not None:
        total_size = response.body.invocation.total_count
        interge = total_size // page_size
        total_page = interge if total_size % page_size == 0 else (
                interge + 1)  # if page_number larger than total page, return the last page result, not empty list.....
        while page_number <= total_page:
            for item in response.body.invocation.invocation_results.invocation_result:
                result_list.append({'instance_id': item.instance_id,
                                    'exit_code': item.exit_code,
                                    'output': base64.b64decode(item.output) if item.output else b""
                                    })
            page_number += 1
            request.page_number = page_number
            response = _send_request(request)
    else:
        logging.info("Failed to get invoke result of id {}".format(invoke_id))
    return result_list


def do_execute_command(instance_list, linux_command_id, windows_command_id):
    windows_list = [item[0] for item in instance_list if item[1] == "windows"]
    linux_list = [item[0] for item in instance_list if item[1] == "linux"]
    result_list = []

    if len(linux_list) > 0:
        linux_invoke_id = invoke_command(linux_list, linux_command_id)
        if linux_invoke_id is None:
            logging.info('invoke command {} failed'.format(linux_invoke_id))
        else:
            logging.info('invoke command {}'.format(linux_invoke_id))
            time.sleep(15)
            result = get_task_output_by_id(linux_invoke_id)
            if len(result) == 0:
                logging.info('get result failed of invoke {}'.format(linux_invoke_id))
            else:
                logging.info('output %s', result)
            result_list.extend(result)
    if len(windows_list) > 0:
        windows_invoke_id = invoke_command(windows_list, windows_command_id)
        if windows_invoke_id is None:
            logging.info('invoke command {} failed'.format(windows_invoke_id))
        else:
            logging.info('invoke command {}'.format(windows_invoke_id))
            time.sleep(15)
            result = get_task_output_by_id(windows_invoke_id)
            if len(result) == 0:
                logging.info('get result failed of invoke {}'.format(windows_invoke_id))
            else:
                logging.info('output %s', result)
            result_list.extend(result)

    return result_list


def chunks(l, n):
    for i in range(0, len(l), n):
        yield l[i:i + n]


def execute_command(instance_list, fix=False):
    global linux_dhcp_script_str
    global windows_dhcp_script_str

    # create Linux axt command
    desc = 'linux_check_dhclient_fix'
    name = desc
    command_str = linux_dhcp_script_str
    # Fix base64 encoding for Python 3
    linux_command_id = create_command(base64.b64encode(command_str.encode('utf-8')).decode('utf-8'), 'RunShellScript',
                                      name, desc)

    if linux_command_id is None:
        logging.info('create linux command failed')
        return
    else:
        logging.info('create command %s %s' % (name, linux_command_id))
        logging.info('-' * 100)
        logging.info(command_str)
        logging.info('-' * 100)

    desc = 'windows_check_dhclient_fix'
    name = desc
    command_str = windows_dhcp_script_str
    # Fix base64 encoding for Python 3
    windows_command_id = create_command(base64.b64encode(command_str.encode('utf-8')).decode('utf-8'),
                                        'RunPowerShellScript', name, desc)

    if windows_command_id is None:
        logging.info('create windows command failed')
        return
    else:
        logging.info('create command %s %s' % (name, windows_command_id))
        logging.info('-' * 100)
        logging.info(command_str)
        logging.info('-' * 100)

    result_all = []
    for iid_list in chunks(instance_list, 50):
        resultout = do_execute_command(iid_list, linux_command_id, windows_command_id)
        if resultout == None:
            raise Exception("Execute the assistant command failed, please check the log files.")
        result_all.extend(resultout)
    return result_all


# send open api request
def _send_request(request):
    runtime = util_models.RuntimeOptions()
    try:
        # Use the appropriate method based on the request type
        if isinstance(request, ecs_models.DescribeInstancesRequest):
            response = clt.describe_instances_with_options(request, runtime)
        elif isinstance(request, ecs_models.DescribeCloudAssistantStatusRequest):
            response = clt.describe_cloud_assistant_status_with_options(request, runtime)
        elif isinstance(request, ecs_models.CreateCommandRequest):
            response = clt.create_command_with_options(request, runtime)
        elif isinstance(request, ecs_models.InvokeCommandRequest):
            response = clt.invoke_command_with_options(request, runtime)
        elif isinstance(request, ecs_models.DescribeInvocationResultsRequest):
            response = clt.describe_invocation_results_with_options(request, runtime)
        else:
            raise Exception("Unsupported request type: {}".format(type(request)))
        logging.info(response)
        return response
    except Exception as e:
        logging.error(e)
        return None


def format_as_table(id, axt, needfix, fixed):
    return "|  {0:^25s}  |  {1:^15s}  |  {2:^15s}  |  {3:^15s}  |".format(id, axt, needfix, fixed)


def format_as_line():
    return "+  {0:-^25s}  +  {0:-^15s}  +  {0:-^15s}  +  {0:-^15s}  +".format("-")


def print_log(content):
    logging.info(content)
    print(content)


def show_output(result_all, status_tuple):
    output_details = []

    noneedfix_list = []
    unknown_list = []
    fixed_list = []
    error_list = []

    # Deal with no axt installed list.
    for tn in status_tuple[1]:
        unknown_list.append(tn[0])
        output_details.append([tn[0], "Not Installed", "Unknown", "Unknown"])

    # Deal with axt installed but not output.
    if result_all == None:
        for ta in status_tuple[1]:
            unknown_list.append(ta[0])
            output_details.append([ta[0], "Installed", "Unknown", "Unknown"])
    else:
        for i in result_all:
            instanceid = i['instance_id']
            outputcontent = i['output']
            logging.info("instance: {}, script execute output:{} ".format(instanceid, outputcontent))
            # 处理字节串和字符串的情况
            content_str = outputcontent.decode('utf-8') if isinstance(outputcontent, bytes) else outputcontent

            if "no need fix" in content_str:
                noneedfix_list.append(instanceid)
                output_details.append([instanceid, "Installed", "No", "NoChange"])
            elif "need fixing" in content_str:
                fixedflag = "Failed"
                if "fix success" in content_str:
                    fixed_list.append(instanceid)
                    fixedflag = "Success"
                else:
                    error_list.append(instanceid)
                output_details.append([instanceid, "Installed", "Yes", fixedflag])
            else:
                unknown_list.append(instanceid)
                output_details.append([instanceid, "Installed", "Unknown", "Unknown"])

        print_log("Scan done.")
        print_log("")
        print_log("Instance Status details:")
        print_log(format_as_line())
        print_log(format_as_table("InstanceID", "Cloud Assistant", "NeedFix", "FixResult"))
        print_log(format_as_line())
        for i in output_details:
            print_log(format_as_table(i[0], i[1], i[2], i[3]))
            print_log(format_as_line())
        print_log("")
        print_log("Summary:")
        print_log("=" * 30)
        print_log("Total instances scanned: {}".format(len(status_tuple[0]) + len(status_tuple[1])))
        print_log("Fixed: {}".format(len(fixed_list)))
        print_log("Unknown: {}".format(len(unknown_list)))
        print_log("Failed: {}".format(len(error_list)))
        logging.info("No need Fixed: {}".format(len(noneedfix_list)))

        print_log("""
Note:
1. For the instances with the "FixResult" colume value as "Unknown" or "Failed", please follow the steps in https://help.aliyun.com/knowledge_detail/94181.html to fix the issue manually.
2. Check the log file under the same folder for more details.
""")


if __name__ == '__main__':
    if len(sys.argv) < 4:
        print('Usage: %s <AccessKeyID> <AccessKeySecret> <region-id>' % sys.argv[0])
        print()
        sys.exit(1)

    # Get access key, secret and region from command line arguments
    access_key = sys.argv[1]
    access_key_secret = sys.argv[2]
    region_name = sys.argv[3]

    # Initialize credentials
    credential_config = CredentialConfig(
        type='access_key',
        access_key_id=access_key,
        access_key_secret=access_key_secret
    )
    credential_client = CredentialClient(credential_config)

    # Initialize ECS client
    config = open_api_models.Config(
        credential=credential_client,
        region_id=region_name
    )
    # Set endpoint
    config.endpoint = f'ecs.{region_name}.aliyuncs.com'
    clt = EcsClient(config)

    instancewos_list_byregion = get_allaffectedinstances()

    if len(instancewos_list_byregion) == 0:
        print_log(
            "We don't find vpc instances that need scan under region {}. Nothing to do, now exit.".format(region_name))
        sys.exit(0)
    print_log("")
    print_log("Performing the scan and this will take some time, please wait...")

    # Check cloud assistant install status. This is needed for script execute.
    # The first tuple: list of axt installed (instanceid, ostype). The second tuple: list of axt not installed (instanceid, ostype).
    status_tuple = check_axtinstalled(instancewos_list_byregion)
    if status_tuple == None:
        raise Exception(
            "failed to check cloud assistant status. Please check the region, ak and instances are correct. ")

    result_all = execute_command(status_tuple[0])

    show_output(result_all, status_tuple)