#!/usr/bin/env nu

use lib.nu *
use cache.nu *
use std
use ../../../../core/nulib/lib_provisioning/utils/templates.nu run_from_template
use ../../../../core/nulib/lib_provisioning/config/accessor.nu *
#use ssh.nu ssh_cmd
#use ssh.nu  scp_to

export def aws_query_servers [
   find: string
   cols: string
] {
   print $find
   print $cols
   print "aws_query_servers"
   exit 1
   let res = (^aws server list -o json err> (if $nu.os-info.name == "windows" { "NUL" } else { "/dev/null" }) | complete)
   if $res.exit_code == 0 {
      $res.stdout  | from json | get servers
   } else {
      if (is-debug-enabled) {
         (throw-error "🛑 aws server list " $"($res.exit_code) ($res.stdout)" "aws query server" --span (metadata $res).span)
      } else {
         print $"🛑 Error aws server list: ($res.exit_code) ($res.stdout | ^grep 'error')"
      }
   }
}
export def aws_server_info [
   server: record
   check: bool
] {
   #--query "Reservations[*].Instances[*].{
   let res = (^aws ec2 describe-instances --out json --filters $'"Name=tag:hostname,Values=($server.hostname)"' --filters "Name=instance-state-name,Values=running"
      --query "Reservations[*].Instances[].{
      id: InstanceId,
      tags: Tags,
      private_ips: NetworkInterfaces[],
      public_ips: PublicIpAddress,
      sgs: SecurityGroups[],
      volumes: BlockDeviceMappings,
      type: InstanceType,
      status: State.Name
      }"
      --output json | complete)
   if $res.exit_code == 0 {
      let data = ($res.stdout  | from json | get -o 0 | default {})
      if ($data | is-empty) {
         {}
      } else {
         ($data | merge { hostname: $server.hostname})
      }
   } else if $check {
         {}
   } else {
      if (is-debug-enabled) {
         (throw-error "🛑 aws server " $"($res.exit_code) ($res.stdout)" $"aws server info ($server.hostname)" --span (metadata $res).span)
      } else {
         print $"🛑 aws server ($server.hostname):($res.stdout | ^grep 'error')"
         {}
      }
   }
}
export def aws_on_prov_server [
   server?: record
] {
   #let info = if ( $env.CURRENT_FILE? | into string ) != "" { (^grep "^# Info:" $env.CURRENT_FILE ) | str replace "# Info: " "" } else { "" }
   #$"From (_ansi purple_bold)AWS(_ansi reset)"
}
export def _aws_query_servers [
   find: string
   cols: string
] {
   return [ { "hostname": "fsfsdf"}  ]
   let res = (^aws server list -o json | complete)
   if $res.exit_code == 0 {
      let result = if $find != "" {
         $res.stdout | from json | get servers | find $find
      } else {
         $res.stdout  | from json | get servers
      }
      if $cols != "" {
         let field_list = ($cols | split row ",")
         $result | select -o $field_list
      } else {
         $result
      }
   } else {
      (throw-error "🛑 aws server list " $"($res.exit_code) ($res.stdout)" "aws query server" --span (metadata $res).span)
   }
}
# infrastructure and services
export def aws [
  args: list<string>     # Args for create command
  --server(-s): record
  --serverpos (-p): int         # Server position in settings
  --check (-c)            # Only check mode no servers will be created
  --wait (-w)             # Wait servers to be created
  --infra (-i): string    # Infra path
  --settings (-s): string # Settings path
  --outfile (-o): string  # Output file
  --debug (-x)            # Use Debug mode
] {
   if $debug { set-debug-enabled true }
   let target = ($args | get -o 0 | default "")
   let task = ($args | get -o 1 | default "")
   let cmd_args = if ($args | length) > 1 { ($args | drop nth ..1) } else { [] }
   match ($task) {
      "help" | "h" => {
         print "TODO aws help"
         if not (is-debug-enabled) { end_run "" }
         exit
      },
      _ => {
         if ($args | find "help" | length) > 0 {
            match $task {
               "server"  => {
                  print "SERVER "
                  aws_server ($args | drop nth ..0)
               },
               "inventory" => {
                  aws_server ($args | drop nth ..0)
               },
               "ssh"  => {
                  aws_server ($args | drop nth ..0)
               },
               "delete"  => {
                  aws_server ($args | drop nth ..0)
                  # ($args | drop nth ..1) --server $server
               },
               _ => {
                  option_undefined "aws" ""
                  print "TODO aws help"
               }
            }
            if not (is-debug-enabled) { end_run "" }
            exit
         }
      }
   }
   #use utils/settings.nu [ load_settings ]
   let curr_settings = if $infra != null {
      if $settings != null {
         (load_settings --infra $infra --settings $settings)
      } else {
         (load_settings --infra $infra)
      }
   } else {
      if $settings != null {
         (load_settings --settings $settings)
      } else {
         (load_settings)
      }
   }
   match ($task) {
      "server"  => {
         print (
            aws_server $cmd_args --server $server --settings $curr_settings --error_exit
         )
      },
      "inventory"  => {
      },
      "ssh"  => {
      },
      "delete"  => {
         # ($args | drop nth ..1) --server $server
      },
      _ => {
         option_undefined "aws" ""
         if not (is-debug-enabled) { end_run "" }
         exit
      }
   }
}
export def aws_get_ip [
   settings: record
   server: record
   ip_type: string
] {
   match $ip_type {
      "private" | "prv" | "priv" =>  {
         $"($server.network_private_ip)"
      },
      _ => {
         let res = (^aws ec2 describe-instances --filter $"Name=tag-value,Values=($server.hostname)" "Name=instance-state-name,Values=running"
               --query "Reservations[*].Instances[0].PublicIpAddress"
               --output text
               err> (if $nu.os-info.name == "windows" { "NUL" } else { "/dev/null" }) | complete
         )
         if $res.exit_code == 0 {
            ($res.stdout  | default {})
         } else { "" }
      }
   }
}
# To create infrastructure and services
export def aws_server [
   args: list<string>     # Args for create command
  --server: record
  --error_exit
  --status
  --serverpos (-p): int         # Server position in settings
  --check (-c)            # Only check mode no servers will be created
  --wait (-w)             # Wait servers to be created
  --infra (-i): string    # Infra path
  --settings (-s): record # Settings path
  --outfile (-o): string  # Output file
  --debug (-x)            # Use Debug mode
] {
   let task = ($args | get -o 0)
   let target = if ($args | length) > 1 { ($args | get -o 1)  } else  { "" }
   let cmd_args = if ($args | length) > 1 { ($args | drop nth ..1) } else { [] }
   match ($task) {
      "help" | "h" | "" => {
         print "TODO aws server help"
         if not (is-debug-enabled) { end_run "" }
         exit
      },
      _ => {
         if $target == "" or ($args | find "help" | length) > 0  {
            match $task {
               "server"  => {
                  aws_server $cmd_args
               },
               "status"  => {
                  print $server
                  print $error_exit
               }
               "inventory"  => {
                  print "TODO aws server inventory help"
               },
               "ssh"  => {
                  print "TODO aws server ssh help"
               },
               "delete"  => {
                  # ($args | drop nth ..1) --server $server
                  #aws_delete_server $cmd_args true
               },
               _ => {
                  option_undefined "aws" "server"
                  print "TODO aws server help"
               }
            }
            if not (is-debug-enabled) { end_run "" }
            exit
         }
      }
   }
   let server_target = if $server != null {
      $server
   } else if $settings != null {
      ($settings.data.servers | where {|it| $it.hostname == $target } | get -o 0)
   } else {
      null
   }
   if $server_target == null {
      if $error_exit {
         let text = $"($args | str join ' ')"
         (throw-error "🛑 aws server" $text "" --span (metadata $server_target).span)
      }
      return ""
   }
   if $status or $task == "status" {
      print "aws server status "
      return true
   }
   match $task {
      "get_ip" => {
         aws_get_ip $settings $server_target ($cmd_args | get -o 0 | default "")
      },
      "stop" => {
         aws_server_state $server_target "stop" false true $settings
      },
      "start" => {
         aws_server_state $server_target "start" false true $settings
      },
      "restart" => {
         aws_server_state $server_target "restart" false true $settings
      },
      _ => {
         option_undefined "aws" "server"
         if not (is-debug-enabled) { end_run "" }
         exit
      }
   }
}
export def aws_create_private_network [
  settings: record
  server: record
  check: bool
] {
   if $server == null {
      print $"❗ No server found in settings "
      return ""
   }
   # new_aws network list  -o json |
   # let net_id =  ($data.networks | get -o 0 ).uuid)
    let zone = ( $server.zone? | default "")
    if $zone == "" {
      print $"($server.hostname) No zone found to CREATE network_privat_id"
      return ""
    }
   let network_private_name = ($server.network_private_name? | default "")
    if $network_private_name == "" {
      print $"($server.hostname) No network_private_name found to CREATE network_privat_id"
      return ""
    }
   let priv_cidr_block = ($server.priv_cidr_block | default "")
    if $network_private_name == "" {
      print $"($server.hostname) No priv_cidr_block found to CREATE network_privat_id"
      return ""
    }
   # EXAMPLE_BASH private_net_id=$(aws network list -o yaml | $YQ '.networks[] | select(.ip_networks.ip_network[].address == "'"$priv_cidr_block"'") | .uuid' 2>/dev/null | sed 's,",,g')
   let result = (^aws "network" "list" "-o" "json" | complete)
   let private_net_id = if $result.exit_code == 0 {
      let data = ($result.stdout | from json )
      ($data | get -o networks | find $priv_cidr_block | get -o 0 | get -o uuid | default "")
   }  else {
      ""
   }
   if $check and $private_net_id == "" {
      print $"❗private_network will be register in a real creation request not in check state"
      return  ""
   } else if $private_net_id == "" {
      let result = (^aws network create --name $network_private_name --zone $zone
       --ip-network $"address='($priv_cidr_block)',dhcp=true" -o json ) | complete
      let new_net_id = if $result.exit_code == 0 {
         ($result.stdout | from json | find $priv_cidr_block | get -o 0 | get -o uuid | default "")
      }  else { "" }
      if $new_net_id  == "" {
         (throw-error $"🛑 no private network ($network_private_name) found"
            $"for server ($server.hostname) ip ($server.network_private_ip)"
               $"aws_check_requirements" --span (metadata $new_net_id.span))
         return false
      }
      # Save changes ...
      #use utils/settings.nu [ save_servers_settings save_settings_file ]
      let match_text = "    network_private_id = "
      let defs_provider_path = $"($settings.data.server_path | get -o 0 | path dirname)/aws_defaults"
      save_servers_setings $settings $match_text $new_net_id
      save_settings_file $settings $"($settings.src_path)/($settings.src)" $match_text $new_net_id
      save_settings_file $settings $"($defs_provider_path)" $match_text $new_net_id
   }
   return true
}
export def aws_get_ssh_key [
  server: record
] {
   let res  = (^aws ec2 describe-key-pairs --key-names $server.ssh_key_name
   --query "KeyPairs[0].KeyPairId" --out text err> (if $nu.os-info.name == "windows" { "NUL" } else { "/dev/null" }) | complete)
   if $res.exit_code != 0 {
      print $"❗Error [($res.exit_code)] read (_ansi blue_bold)($server.provider)(_ansi reset) ssh_key (_ansi red_bold)($server.ssh_key_name)(_ansi reset) for server (_ansi green_bold)($server.hostname)(_ansi reset)"
      ""
   } else {
      ($res.stdout | str trim)
   }
}
export def aws_create_ssh_key [
  server: record
] {
   let res = (^aws ec2 import-key-pair --key-name $server.ssh_key_name --public-key-material $"fileb://($server.ssh_key_path)"
   --query "KeyPairs[0].KeyPairId" --out text err> (if $nu.os-info.name == "windows" { "NUL" } else { "/dev/null" }) | complete)
   if $res.exit_code != 0 {
      print $"❗Error [($res.exit_code)] create (_ansi blue_bold)($server.provider)(_ansi reset) ssh_key (_ansi red_bold)($server.ssh_key_name)(_ansi reset) for server (_ansi green_bold)($server.hostname)(_ansi reset)"
      ""
   } else {
      print $"✅ (_ansi blue_bold)($server.provider)(_ansi reset) create ssh_key (_ansi cyan_bold)($server.ssh_key_name)(_ansi reset) for server (_ansi green_bold)($server.hostname)(_ansi reset)"
      ($res.stdout | str trim)
   }
}
export def aws_check_server_requirements [
  settings: record
  server: record
  check: bool
] {
   print $"Check (_ansi blue)($server.provider)(_ansi reset) requirements for (_ansi green_bold)($server.hostname)(_ansi reset)"
   if $server.provider != "aws" { return false }
   if (^aws account get-contact-information --query "ContactInformation" --out text err> (if $nu.os-info.name == "windows" { "NUL" } else { "/dev/null" }) | complete).exit_code != 0 {
      return false
   }
   if ($server.ssh_key_name | default "" | is-empty) {
      print $"❗server (_ansi green_bold)($server.hostname)(_ansi reset) ssh_key (_ansi red_bold)($server.ssh_key_name)(_ansi reset) not found "
      return false
   }
   let key_pair = (aws_get_ssh_key $server)
   if ($key_pair | is-not-empty) { return true }
   if $check {
      print $"❗server (_ansi green_bold)($server.hostname)(_ansi reset) ssh_key (_ansi red_bold)($server.ssh_key_name)(_ansi reset) not found in (_ansi blue_bold)($server.provider)(_ansi reset)"
      return true
   }
   if ($server.ssh_key_path | default "" | is-empty) or not ($server.ssh_key_path | path exists) {
      print $"❗Error create (_ansi blue)($server.provider)(_ansi reset) for server (_ansi green_bold)($server.hostname)(_ansi reset) ssh_key (_ansi red_bold)($server.ssh_key_name)(_ansi reset)"
      print $"❗server (_ansi green_bold)($server.hostname)(_ansi reset) ssh_key_path (_ansi red_bold)($server.ssh_key_path)(_ansi reset) for ssh_key (_ansi red_bold)($server.ssh_key_name)(_ansi reset) not found "
      return false
   }
   let key_pair = (aws_create_ssh_key $server)
   if ($key_pair | is-empty) {
      print $"❗server (_ansi green_bold)($server.hostname)(_ansi reset) ssh_key (_ansi red_bold)($server.ssh_key_name)(_ansi reset) not found in (_ansi blue_bold)($server.provider)(_ansi reset)"
      return false
   }
   return true
      let private_net_id = if ($server.network_private_id? | default "") == "CREATE" {
            print $"❗ ($server.network_private_id?) found will be created "
            (aws_create_private_network $settings $server $check)
      } else {
            (aws_create_private_network $settings $server $check)
         ($server.network_private_id? | default "" )
      }
      let result = (^aws "network" "show" $private_net_id "-o" "json" | complete)
      let privavet_net_id = if (not $check) and $result.exit_code != 0 {
         let net_id = (aws_create_private_network $settings $server $check)
         let res = (^aws "network" "show" $private_net_id "-o" "json" | complete)
         if $res.exit_code != 0 {
            print  $"❗Error: no ($private_net_id) found "
            " "
         } else {
            let data = ($result.stdout | from json )
            ($data.networks | get -o 0 | get -o uuid)
         }
      } else if $result.exit_code == 0 {
         let data = ($result.stdout | from json)
         ($data.uuid)
      } else {
         ""
      }
      let server_private_ip = ($server.network_private_ip? | default "")
      if $private_net_id == "" and  $server_private_ip != "" {
         (throw-error $"🛑 no private network ($private_net_id) found"
            $"for server ($server.hostname) ip ($server_private_ip)"
            "aws_check_requirements" --span (metadata $server_private_ip).span)
         return false
      }
   true
}

export def aws_make_settings [
  settings: record
  server: record
] {

#  #  _delete_settings
   let out_settings_path = $"($settings.infra_fullpath)/($server.provider)_settings.yaml"
   let data = if ($out_settings_path | path exists ) {
      (open $out_settings_path | from yaml)
   } else {
      null
   }
   let task = if $data != null { "update" } else { "create" }
   let uuid  = (^aws server show $server.hostname "-o" "json" | from json).uuid? | default ""
   if $uuid == "" {
      returm false
   }
   let ip_pub = (aws_get_ip $settings $server "public")
   let ip_priv = (aws_get_ip $settings $server "private")

   let server_settings = {
      name: $server.hostname,
      id: $uuid,
      private_net: {
         id: $server.network_private_id
         name: $server.network_private_name
      },
      zone: $server.zone,
      datetime: (get-now)
      ip_addresses:  {
         pub: $ip_pub, priv: $ip_priv
      }
   }
   let new_data = if $data != null and $data.servers? != null {
      ( $data.servers | each { |srv|
         where {|it| $it.name != $server.hostname }
      }) | append $server_settings
   }  else {
      ## create data record
      {
         servers: [ $server_settings ]
      }
   }
   $new_data | to yaml | save --force $out_settings_path
   print $"✅ aws settings ($task) -> ($out_settings_path)"
   true
}
export def aws_delete_settings [
   settings: record
   server: record
] {
}
export def aws_wait_storage [
   settings: record
   server: record
   new_state: string
   id: string
] {
   let state = (^aws ec2 describe-volumes --volume-ids $id --query "Volumes[0].State")
   if ($state | str contains $new_state) { return true }
   print $"Checking volume ($id) state for (_ansi blue_bold)($server.hostname)(_ansi reset) state (_ansi yellow_bold)($new_state)(_ansi reset) ..."
   let val_timeout = if $server.running_timeout? != null { $server.running_timeout } else { 60 }
   let wait = if $server.running_wait? != null { $server.running_wait } else { 10 }
   let wait_duration = ($"($wait)sec"| into duration)
   mut num = 0
   while true {
      let status = (^aws ec2 describe-volumes --volume-ids $id --query "Volumes[0].State")
      if ($status | str contains $new_state) {
         return true
      } else if $val_timeout > 0 and $num > $val_timeout {
         print ($"\n🛑 (_ansi red)Timeout(_ansi reset) ($val_timeout) volume ($id) state for (_ansi blue)($server.hostname)(_ansi reset) " +
            $"(_ansi blue_bold)($new_state)(_ansi reset)  (_ansi red_bold)failed(_ansi reset) "
         )
         return false
      } else {
         $num = $num + $wait
         if (is-debug-enabled) {
           print ($"(_ansi blue_bold) 🌥 (_ansi reset) volume state for (_ansi yellow)($id)(_ansi reset) " +
               $"for (_ansi green)($server.hostname)(_ansi reset)-> ($status | str trim) "
            )
         } else {
           print -n $"(_ansi blue_bold) 🌥 (_ansi reset)"
         }
         sleep $wait_duration
      }
   }
   false
}
export def aws_create_storage [
   settings: record
   server: record
   server_info: record
   storage: record
   volumes: list
   total_size: int
] {
   if $total_size <= 0 {
      print $"❗ Create storage for ($server.hostname) size (_ansi red)($total_size) error(_ansi reset)"
      return {}
   }
   let av_zone = if ($storage.item | get -o zone | is-empty) {
      ($volumes | get -o 0 | get -o AvailabilityZone)
   } else {
      ($storage.item | get -o zone)
   }
   if ($av_zone | is-empty) {
      print ($"❗ Create storage for (_ansi green_bold)($server.hostname)(_ansi reset) " +
         $"(_ansi cyan_bold)($total_size)(_ansi reset) (_ansi red)AvailavilityZone  error(_ansi reset)"
      )
      return {}
   }
   let vol_device = if ($storage.item | get -o voldevice | str contains "/dev/") {
      ($storage.item | get -o voldevice)
   } else {
      ("/dev/" | path join ($storage.item | get -o voldevice))
   }
   if ($vol_device | is-empty) {
      print ($"❗ Create storage for ($server.hostname)  (_ansi  cyan_bold)($total_size)(_ansi reset) in " +
         $"(_ansi blue_bold)($av_zone)(_ansi reset) (_ansi red)voldevice error(_ansi reset)"
      )
      return {}
   }
   let op_encrypted = if ($storage.item | get -o encrypted | default false) {
      "--encrypted"
   } else {
      "--no-encrypted"
   }
   let res_create = (^aws ec2 create-volume --volume-type ($storage.item | get -o voltype) --size $total_size --availability-zone $av_zone $op_encrypted | complete)
   if $res_create.exit_code != 0 {
      print ($"❗ Create storage for ($server.hostname) (_ansi  cyan_bold)($total_size)(_ansi reset) in " +
         $"(_ansi blue_bold)($av_zone)(_ansi reset) with ($vol_device) (_ansi red)error(_ansi reset) ($res_create.stdout)"
      )
      return {}
   }
   let instance_id = ($server_info | get -o InstanceId | default "")
   let vol = ($res_create.stdout | from json)
   let vol_id = ($vol | get -o volumeId)
   let new_state = "available"
   if not (aws_wait_storage $settings $server $new_state $vol_id) {
      print ($"❗ Error ($vol_id) storage for ($server.hostname) (_ansi  cyan_bold)($total_size)(_ansi reset) device ($vol_device) " +
         $"in (_ansi blue_bold)($av_zone)(_ansi reset) errors not in (_ansi red)($new_state)(_ansi reset) state"
      )
      ^aws ec2 delete-volume --volume-id $vol_id
      print $"❗ Attach ($vol_id) deleted"
      return {}
   }
   if ($instance_id | is-empty) { return $vol }
   let res_attach = (^aws ec2 attach-volume --volume-id $vol_id --device $vol_device --instance-id $instance_id | complete)
   if $res_attach.exit_code != 0 {
      print ($"❗ Attach ($vol_id) storage for (_ansi green_bold)($server.hostname)(_ansi reset) (_ansi cyan_bold)($total_size)(_ansi reset) " +
         $"device ($vol_device) in (_ansi blue_bold)($av_zone)(_ansi reset) (_ansi red)errors(_ansi reset) " # ($res.stdout)"
      )
      ^aws ec2 delete-volume --volume-id $vol_id
      print $"❗ Attach (_ansi red_bold)($vol_id)(_ansi reset) deleted"
   }
   let res_vol = (^aws ec2 describe-volumes --volume-id $vol_id --filters $"Name=attachment.instance-id,Values=($instance_id)"
         --query "Volumes[]" --output=json | complete)
   if $res_vol.exit_code == 0 {
      print ($"✅ Atached (_ansi yellow)($vol_id)(_ansi reset) storage for ($server.hostname) (_ansi cyan_bold)($total_size)(_ansi reset) " +
         $"device ($vol_device) in (_ansi blue_bold)(_ansi blue_bold)($av_zone)(_ansi reset)(_ansi reset)"
      )
      ($res_vol.stdout | from json | get -o 0)
   } else {
      print ($"❗ Volume ($vol_id) storage for ($server.hostname)  (_ansi  cyan_bold)($total_size)(_ansi reset) " +
         $"device ($vol_device) in (_ansi blue_bold)(_ansi blue_bold)($av_zone)(_ansi reset)(_ansi reset) (_ansi red)errors(_ansi reset) ($res_vol.stdout)"
      )
      {}
   }
}
def aws_vol_modify [
   settings: record
   server:   record
   store_size: int
   vol_id: string
   vol_size: int
] {
   if $store_size <= 0 {
      print $"🛑 new vol size (_ansi red_bold)($store_size)(_ansi reset) for (_ansi yellow)($vol_id)(_ansi reset) (_ansi green_bold)($server.hostname)(_ansi reset)"
      return false
   }
   let curr_size = (^aws ec2 describe-volumes --volume-ids $vol_id --query "Volumes[0].Size")
   if $curr_size == $vol_size { return true }
   let res_modify = (^aws ec2 modify-volume --size $store_size --volume-id $vol_id | complete)
   if $res_modify.exit_code != 0 {
      print  $"❗Modify ($vol_id) from ($vol_size) to ($store_size) for ($server.hostname) in ($server.provider) error "
      if (is-debug-enabled) { print $res_modify.stdout }
      return false
   }
   let new_state = "in-use"
   let wait = if $server.running_wait? != null { $server.running_wait } else { 10 }
   let wait_duration = ($"($wait)sec"| into duration)
   print ($"(_ansi blue_bold) 🌥 (_ansi reset) waiting for volume (_ansi yellow)($vol_id)(_ansi reset) " +
      $"for (_ansi green)($server.hostname)(_ansi reset)-> ($new_state) "
   )
   sleep $wait_duration
   (aws_wait_storage $settings $server $new_state $vol_id)
}
def aws_part_resize [
   settings: record
   server:   record
   mount_path: string
] {
   let ip = (mw_get_ip $settings $server $server.liveness_ip false )
   if $ip == "" {
      print $"🛑 No IP found for (_ansi green_bold)($server.hostname)(_ansi reset)"
      return false
   }
   let template_name = "resize_storage"
   let template_path = ((get-templates-path) | path join $"($template_name).j2")
   let wk_file = $"($settings.wk_path)/($server.hostname)_($template_name)_cmd"
   let wk_vars = $"($settings.wk_path)/($server.hostname)_($template_name)_vars.((get-provisioning-wk-format))"
   let run_file = $"($settings.wk_path)/on_($server.hostname)_($template_name)_run.sh"
   let data_settings = ($settings.data | merge { wk_file: $wk_file, now: (get-now),  provisioning_vers: ((get-provisioning-vers) | str replace "null" ""),
      provider: ($settings.providers | where {|it| $it.provider == $server.provider} | get -o 0  | get -o settings | default {}),
      server: $server })
   if (get-provisioning-wk-format) == "json" {
      $data_settings |  to json  | save --force $wk_vars
   } else {
      $data_settings | to yaml  | save --force $wk_vars
   }
   let resize_storage_sh = ($settings.wk_path | path join $"($server.hostname)-($template_name).sh")
   let result = (run_from_template $template_path $wk_vars $run_file $resize_storage_sh --only_make)
   if $result and ($resize_storage_sh | path exists) {
      open $resize_storage_sh | str replace "$MOUNT_PATH" $mount_path | save --force $resize_storage_sh
       let target_cmd = $"/tmp/($template_name).sh"
       #use ssh.nu scp_to ssh_cmd
       if not (scp_to $settings $server [$resize_storage_sh] $target_cmd $ip) { return false }
       print $"Running (_ansi blue_italic)($target_cmd | path basename)(_ansi reset) in (_ansi green_bold)($server.hostname)(_ansi reset)"
       if not (ssh_cmd $settings $server true $target_cmd $ip) { return false }
       if (is-ssh-debug-enabled) { return true }
       if not $env.PROVISIONING_DEBUG {
         (ssh_cmd $settings $server false $"rm -f ($target_cmd)" $ip)
       }
      true
   } else {
      false
   }
}
export def aws_post_create_server [
   settings: record
   server: record
   check: bool
] {
   if $server != null {
      (aws_storage_fix_size $settings $server 0)
   } else {
      true
   }
   # let provider_path = (get_provider_data_path $settings $server)
   # #use lib_provisioning/utils/settings.nu load_provider_env
   # #let data = (load_provider_env $settings $server $provider_path)
   # aws_scan_settings "scan" $provider_path $settings $server false
   # aws_scan_servers $provider_path $settings $server
   # # let prov_settings = (load_provider_env $settings $server $provider_path)
}
export def aws_modify_server [
   settings: record
   server:   record
   new_values: list
   error_exit: bool
] {
   #let res = (^aws ec2 describe-instances --filter $"Name=tag-value,Values=($server.hostname)" "Name=instance-state-name,Values=running"
   # TODO fix for AWS
   return
   let res = (^aws ec2  server $server.hostname  modify ...($new_values)  | complete)
   if $res.exit_code != 0 {
      print $"❗ Server ($server.hostname)  modify ($new_values | str join ' ') errors ($res.stdout ) "
      if $error_exit {
         exit 1
      } else {
         return "error"
      }
   }
}
def aws_get_volume [
   vol_id: string
   instance_id: string
] {
   let res_vol = (^aws ec2 describe-volumes --volume-id $vol_id --filters $"Name=attachment.instance-id,Values=($instance_id)"
   --query "Volumes[]" --output=json | complete)
   if $res_vol.exit_code == 0 {
      let vol = ($res_vol.stdout | from json | get -o 0)
      #if ($vol | get -o SnapshotId | is-empty) {
      $vol
      #}
   } else {
      {}
   }
}
def aws_get_all_volumes [
   instance_id: string
   instance_data: record

] {
   $instance_data | get -o BlockDeviceMappings | default [] | each {|device|
      let vol_id = ($device | get -o Ebs | get -o VolumeId | default "")
      if ($vol_id | is-not-empty) {
         (aws_get_volume $vol_id $instance_id)
      }
   }
}
export def aws_storage_fix_size [
   settings: record
   server:   record
   storage_pos: int
] {
   let res = (^aws ec2 describe-instances --out json --filters $'"Name=tag:hostname,Values=($server.hostname)"' --filters "Name=instance-state-name,Values=running"
      --query "Reservations[*].Instances[]" # ?State.Name!='terminated']
      err> (if $nu.os-info.name == "windows" { "NUL" } else { "/dev/null" }) | complete
   )
   if $res.exit_code != 0 {
      print  $"❗Error: no info found for ($server.hostname) in ($server.provider) "
      return false
   }
   let instance_data = ($res.stdout | from json | get -o 0 | default {} | into record)
   let instance_id = ($instance_data | get -o InstanceId | default "")
   let storages = ($server | get -o storages)
   let volumes = (aws_get_all_volumes $instance_id $instance_data)

   mut req_storage = false
   for storage in ($storages | enumerate) {
      let store_size = ($storage.item | get -o size | default 0)
      let store_total = ($storage.item | get -o total | default 0)
      if $store_total == 0 { continue }
      let store_name = ($storage.item | get -o name | default "")
      let volume = ($volumes | get -o $storage.index | default {})
      let vol_size = ($volume | get -o Size | default 0)
      let vol_id = ($volume | get -o VolumeId | default "")

      let res_vol = (^aws ec2 describe-volumes --volume-id $vol_id --filters $"Name=attachment.instance-id,Values=($instance_id)"
        --query "Volumes[]" --output=json | complete)
      let store_parts = ($storage.item | get -o parts)
      if ($volume | is-not-empty) {
          if ($store_parts | length) == 0 {
            if $vol_size != $store_size {
               if $vol_size < $store_total {
                  print  $"Store total ($store_total) < ($vol_size) for ($server.hostname) in ($server.provider) "
               }
               let store_mount_path = ($storage.item | get -o mount_path | default "")
               if $store_mount_path == "/" or $store_name == "root" {
                  if (aws_vol_modify $settings $server $store_size $vol_id $vol_size) {
                     aws_part_resize $settings $server $store_mount_path
                     if not $req_storage { $req_storage = true }
                  }
               }
            }
         } else if ($store_parts | length) > 0 {
            let sum_size_parts = ($store_parts | each {|part| $part | get -o size | default 0} | math sum)
            if $vol_size != $sum_size_parts {
               if (is-debug-enabled) {
                  print  $"Store total ($store_total) < ($vol_size) parts ($sum_size_parts) for ($server.hostname) in ($server.provider) "
                  print $store_parts
               }
               $store_parts | each {|part|
                  let part_mount_path = ($part | get -o mount_path)
                  let part_name = ($part | get -o name)
                  let volume_parts = (aws_get_volume $vol_id $instance_id)
                  let volume_parts_size = ($volume_parts | get -o Size | default 0)
                  if $part_mount_path == "/" or $part_name == "root" {
                     let part_size = ($part | get -o size)
                     if $volume_parts_size < $part_size and (aws_vol_modify $settings $server $part_size $vol_id $volume_parts_size)  {
                        aws_part_resize $settings $server $part_mount_path
                     }
                  } else {
                     if $volume_parts_size < $sum_size_parts {
                        if not (aws_vol_modify $settings $server $sum_size_parts $vol_id $volume_parts_size) {
                           print  $"❗Error store vol ($vol_id) modify  to ($volume_parts_size) for ($server.hostname) in ($server.provider) "
                        }
                     }
                  }
               }
               if not $req_storage { $req_storage = true }
            }
         }
      } else {
         print $"($store_size) ($store_total)"
         let volume = if ($store_parts | length) == 0 {
            print "Create storage volume"
            (aws_create_storage $settings $server $instance_data $storage $volumes $store_total)
         } else {
            print "Create storage partitions"
            if (is-debug-enabled) { print $store_parts }
            let sum_size_parts = ($store_parts | each {|part| $part | get -o size | default 0} | math sum)
            (aws_create_storage $settings $server $instance_data $storage $volumes $sum_size_parts)
         }
         if not $req_storage { $req_storage = true }
      }
   }
   if $req_storage {
      "storage"
   } else {
      ""
   }
}
export def aws_status_server [
   hostname: string
   id: string
] {
   let res = if ($id | is-not-empty) {
      (^aws ec2 describe-instances --instance-ids ($id | str trim)
         --query "Reservations[*].Instances[0].[InstanceId,State.Name]"
         --output text
         | complete
         #err> (if $nu.os-info.name == "windows" { "NUL" } else { "/dev/null" }) | complete
      )
   } else {
      (^aws ec2 describe-instances --filter $"Name=tag-value,Values=($hostname)"
         --query "Reservations[*].Instances[0].[InstanceId,State.Name]"
         --output text
         err> (if $nu.os-info.name == "windows" { "NUL" } else { "/dev/null" }) | complete
      )
   }
   if $res.exit_code != 0 {
      print $"❗ status ($hostname) errors ($res.stdout ) "
      return "??"
   }
   ($res.stdout | default "")
}
export def aws_server_state [
   server: record
   new_state: string
   error_exit: bool
   wait: bool
   settings: record
] {
   let res = (^aws ec2 describe-instances --filter $"Name=tag-value,Values=($server.hostname)"
      --query "Reservations[*].Instances[0].[InstanceId,State.Name]"
      --output json
      err> (if $nu.os-info.name == "windows" { "NUL" } else { "/dev/null" }) | complete
   )
   if $res.exit_code != 0 {
      if $error_exit {
         print $"❗ state ($server.hostname) to ($new_state) errors ($res.stdout ) "
         exit 1
      } else {
         return false
      }
   }
   let data = ($res.stdout  | from json | get -o 0 | default [])
   let instance_id = ($data  | get -o 0 | default "")
   let curr_state = ($data  | get -o 1 | default "")
   if ($instance_id |is-empty) {
      print $"❗ state ($server.hostname) to ($new_state) errors (_ansi red)no server found(_ansi reset) "
      return false
   }
   if ($curr_state |is-empty) {
      print $"❗ state ($server.hostname) to ($new_state) errors (_ansi red)no current state found(_ansi reset) "
      return false
   }
   match $new_state {
      "start" if ($curr_state | str contains "running") =>  {
         print $"❗ state ($server.hostname) to ($new_state) error is already (_ansi green)running(_ansi reset)"
         return false
      },
      "stop" if ($curr_state | str contains "stopp") =>  {
         print $"❗ state ($server.hostname) to ($new_state) error is (_ansi green)($curr_state)(_ansi reset)"
         return false
      }
      "stop"  if (aws_has_disable_stop $server $instance_id)  => {
         print $"❗ state ($server.hostname) to ($new_state) error settings (_ansi red)disabled_stop ($server.disable_stop)(_ansi reset)"
         print $"   Server DisableApiStop = true."
         print ( $"   Only (_ansi yellow)restart(_ansi reset) (_ansi default_dimmed)[aws reboot](_ansi reset) " +
            $"or (_ansi yellow)delete(_ansi reset) (_ansi default_dimmed)[aws terminate](_ansi reset) to keep public IPs"
         )
         return false
      }
   }
   let state_data = match $new_state {
      "start" | "restart
       " => {
         print $"($new_state) for ($server.hostname) in ($server.provider)"
         let task_key = if $new_state == "restart" { "reboot" } else { $new_state }
         { name: "running", res:  (^aws ec2 $"($task_key)-instances" --instance-ids $instance_id err> (if $nu.os-info.name == "windows" { "NUL" } else { "/dev/null" }) | complete) }
      },
      "stop" => {
         print $"($new_state) for ($server.hostname) in ($server.provider)"
         { name: "stopped", res:  (^aws ec2 stop-instances --instance-ids $instance_id err> (if $nu.os-info.name == "windows" { "NUL" } else { "/dev/null" }) | complete) }
      },
   }
   if $state_data.res.exit_code != 0 {
      print $"❗ state ($server.hostname) to ($new_state) errors ($res.stdout ) "
      return false
   }
   if $wait { aws_change_server_state $settings $server $state_data.name $instance_id }
   true
}
export def aws_server_exists [
   server: record
   error_exit: bool
] {
   let res = (^aws ec2 describe-instances --filter $"Name=tag-value,Values=($server.hostname)"
      --query "Reservations[*].Instances[0].[InstanceId,State.Name]"
      --output text
      err> (if $nu.os-info.name == "windows" { "NUL" } else { "/dev/null" }) | complete
   )
   if $res.exit_code != 0 {
      if $error_exit {
         print $"❗ status ($server.hostname) errors ($res.stdout ) "
         exit 1
      } else {
         return false
      }
   }
   ($res.stdout  | lines | where {|it| $it | str contains "running" } | length) > 0
}
export def aws_server_is_running [
   server: record
   error_exit: bool
] {
   let res = (^aws ec2 describe-instances --filter $"Name=tag-value,Values=($server.hostname)"
      --query "Reservations[*].Instances[0].State.Name"
      --output text
      err> (if $nu.os-info.name == "windows" { "NUL" } else { "/dev/null" }) | complete
   )
   if $res.exit_code != 0 {
      print $"❗ status ($server.hostname) errors ($res.stdout ) "
      if $error_exit {
         exit 1
      } else {
         return false
      }
   }
   ($res.stdout | str contains "running" | default false)
}
export def aws_change_server_state [
   settings: record
   server: record
   new_state: string
   id: string
   ops: string = ""
] {
   print $"Checking (_ansi blue_bold)($server.hostname)(_ansi reset) state (_ansi yellow_bold)($new_state)(_ansi reset) ..."
   let state = (aws_status_server $server.hostname $id)
   #if $state == "" { return false }
   if ($state | str contains $new_state) { return true }
   let val_timeout = if $server.running_timeout? != null { $server.running_timeout } else { 60 }
   let wait = if $server.running_wait? != null { $server.running_wait } else { 10 }
   let wait_duration = ($"($wait)sec"| into duration)
   mut num = 0
   while true {
      let status = (aws_status_server $server.hostname $id)
      if ($status | str contains $new_state) {
         return true
      #} else if $status == ""  {
      #   return false
      #} else if ($status | str contains "maintenance") == false {
      #   print " "
      #   break
      } else if $val_timeout > 0 and $num > $val_timeout {
         print $"\n🛑 (_ansi red)Timeout(_ansi reset) ($val_timeout) (_ansi blue)($server.hostname)(_ansi reset) (_ansi blue_bold)($new_state)(_ansi reset)  (_ansi red_bold)failed(_ansi reset) "
         return false
      } else {
         $num = $num + $wait
         if (is-debug-enabled) {
           print $"(_ansi blue_bold) 🌥 (_ansi reset) (_ansi green)($server.hostname)(_ansi reset)-> ($status | str trim) "
         } else {
           print -n $"(_ansi blue_bold) 🌥 (_ansi reset)"
         }
         sleep $wait_duration
      }
   }
   false
}
export def aws_delete_server_storage [
   settings: record
   server: record
   error_exit: bool
] {
   let res = (^aws ec2 describe-volumes --filters $"Name=tag-value,Values=($server.hostname)*" --query "Volumes[*].VolumeId" --output json | complete)
   if $res.exit_code == 0 {
      let data = ($res.stdout | from json)
      $data | default [] | each {|vol|
         let res = (^aws ec2 delete-volume --volume-id $vol err> (if $nu.os-info.name == "windows" { "NUL" } else { "/dev/null" }) | complete)
            if $res.exit_code != 0 {
               print $"❗ Delete volume (_ansi blue_bold) ($vol) from ($server.hostname)(_ansi reset) (_ansi red_bold)errors(_ansi reset) ($res.stdout ) "
               continue
            }
            print $"volume ($vol) from (_ansi blue_bold)($server.hostname)(_ansi reset) (_ansi green_bold)deleted(_ansi reset) ($res.stdout ) "
      }
   }
   true
}
export def aws_delete_server [
   settings: record
   server: record
   keep_storage: bool
   error_exit: bool
] {
  let res = (^aws ec2 describe-instances --filter $"Name=tag-value,Values=($server.hostname)" "Name=instance-state-name,Values=running"
      --query "Reservations[*].Instances[*].InstanceId"
      --output text
      err> (if $nu.os-info.name == "windows" { "NUL" } else { "/dev/null" }) | complete
   )
   if $res.exit_code == 0 {
      for id in ($res.stdout | str trim | split row " ") {
         if (aws_has_disable_stop $server $id) {
            print $"Change (_ansi yellow)disableApiStop(_ansi reset) for (_ansi blue_bold)($server.hostname)(_ansi reset) ..."
            ^aws ec2 modify-instance-attribute --instance-id $id --attribute disableApiStop --value false
         }
         let vols = if $keep_storage {
            []
         } else {
            let res_vols = (^aws ec2 describe-volumes --filters $"Name=attachment.instance-id,Values=($id)" --query "Volumes[*].VolumeId" --output json | complete)
            if $res_vols.exit_code == 0 {
              ($res_vols.stdout | from json)
            } else { [] }
         }
         let res = (^aws ec2 terminate-instances --instance-ids $"($id | str trim)" err> (if $nu.os-info.name == "windows" { "NUL" } else { "/dev/null" }) | complete)
         if $res.exit_code != 0 {
            #print $"❗ Delete (_ansi blue_bold)($server.hostname)(_ansi reset) (_ansi red_bold)errors(_ansi reset) ($res.stdout ) "
            continue
         }
         aws_change_server_state $settings $server "terminated" $id
         print $"(_ansi blue_bold)($server.hostname)(_ansi reset) (_ansi green_bold)deleted(_ansi reset) "
         for vol in $vols {
            if not $keep_storage {
               let res = (^aws ec2 delete-volume --volume-id ($vol) err> (if $nu.os-info.name == "windows" { "NUL" } else { "/dev/null" }) | complete)
               if $res.exit_code != 0 {
                  # print $"❗ Delete volume (_ansi blue_bold) ($vol) from ($server.hostname)(_ansi reset) (_ansi red_bold)errors(_ansi reset) ($res.stdout | str trim) "
                  continue
               }
               print $"volume ($vol) from (_ansi blue_bold)($server.hostname)(_ansi reset) (_ansi green_bold)deleted(_ansi reset) ($res.stdout | str trim) "
            } else {
               print $"volume ($vol) from (_ansi blue_bold)($server.hostname)(_ansi reset) (_ansi green_bold)deleted(_ansi reset) ($res.stdout | str trim) "
            }
         }
      }
   }
   if not $keep_storage {
      aws_delete_server_storage $settings $server $error_exit
   }
   true
}

export def aws_server_id [
   server: record
] {
   let res = (^aws ec2 describe-instances --filter $"Name=tag-value,Values=($server.hostname)" "Name=instance-state-name,Values=running"
   --query "Reservations[*].Instances[0].InstanceId"
   --output text
      err> (if $nu.os-info.name == "windows" { "NUL" } else { "/dev/null" }) | complete
   )
   if $res.exit_code != 0 or ($res.stdout | is-empty) {
      print $"❗ No id found for server ($server.hostname) error"
      return ""
   }
   ($res.stdout | default "")
}
export def aws_has_disable_stop [
   server: record
   id: string
] {
   let instance_id = if ($id | is-empty) {
      (aws_server_id $server)
   } else { $id }
   let res =  (^aws ec2 describe-instance-attribute --instance-id $instance_id
      --attribute disableApiStop --query "DisableApiStop.Value" --output text
      err> (if $nu.os-info.name == "windows" { "NUL" } else { "/dev/null" }) | complete
   )
   if $res.exit_code != 0 or ($res.stdout | is-empty) {
      print $"❗ No value found for server ($server.hostname) DisableApiStop "
      return false
   }
   true
}
