|
#!/usr/bin/env bash |
|
set -euo pipefail |
|
|
|
# Configuration defaults |
|
: "${BUNDLE_PATH:=.}" |
|
: "${KEEP_FILES:=0}" |
|
: "${NO_TAR:=0}" |
|
|
|
# Color output functions |
|
say() { |
|
local color="${1}" |
|
shift |
|
echo -e "\033[1;${color}m${*}\033[0m" |
|
} |
|
|
|
say_green() { say 32 "${@}"; } |
|
say_red() { say 31 "${@}"; } |
|
say_yellow() { say 33 "${@}"; } |
|
say_magenta(){ say 35 "${@}"; } |
|
|
|
# Validate required commands |
|
check_dependencies() { |
|
local deps=("kubectl" "jq" "tar") |
|
for dep in "${deps[@]}"; do |
|
if ! command -v "${dep}" >/dev/null 2>&1; then |
|
say_red "Error: Required command '${dep}' not found" |
|
exit 1 |
|
fi |
|
done |
|
} |
|
|
|
parse_args() { |
|
while (($#)); do |
|
case "${1}" in |
|
--ns|-n) shift; NAMESPACE="${1}" ;; |
|
--vm-name|-v) shift; VM_NAME="${1}" ;; |
|
--bundle-path) shift; BUNDLE_PATH="${1}" ;; |
|
--keep) KEEP_FILES=1 ;; |
|
--no-tar) NO_TAR=1 ;; |
|
*) say_red "Unknown argument: ${1}"; exit 1 ;; |
|
esac |
|
shift |
|
done |
|
|
|
# Validate required parameters |
|
if [[ -z "${NAMESPACE:-}" || -z "${VM_NAME:-}" ]]; then |
|
say_red "Missing required parameters:" |
|
say_red "Namespace: ${NAMESPACE:-[unset]}" |
|
say_red "VirtualMachine: ${VM_NAME:-[unset]}" |
|
exit 1 |
|
fi |
|
} |
|
|
|
dump_system_logs() { |
|
local output_dir="${1}/d8-virtualization/" |
|
mkdir -p "${output_dir}" |
|
|
|
|
|
local pods=$(kubectl -n d8-virtualization get pods -o name | cut -d/ -f2) |
|
for pod in ${pods}; do |
|
say_yellow "- Processing pod: ${pod}" |
|
if kubectl logs -n d8-virtualization "${pod}" > "${output_dir}/${pod}.log"; then |
|
echo " Logs saved to: ${pod}.log" |
|
fi |
|
done |
|
} |
|
|
|
dump_resource() { |
|
local namespace="${1}" resource_type="${2}" resource_name="${3}" output_dir="${4}" prefix="${5}" |
|
local yaml_file="${output_dir}/${prefix}${resource_type,,}-${resource_name}.yaml" |
|
|
|
say_yellow " - ${resource_type} ${resource_name}" |
|
kubectl -n "${namespace}" get "${resource_type}" "${resource_name}" -o yaml > "${yaml_file}" |
|
if [[ ${?} -eq 0 ]]; then |
|
echo " Saved to: ${yaml_file##*/}" |
|
else |
|
say_red " Failed to get ${resource_type} ${resource_name}" |
|
rm -f "${yaml_file}" |
|
return 1 |
|
fi |
|
} |
|
|
|
dump_block_device() { |
|
local namespace="${1}" bd_kind="${2}" bd_name="${3}" output_dir="${4}" |
|
|
|
output_dir="${output_dir}/${bd_kind,,}-${bd_name}" |
|
mkdir -p "${output_dir}" |
|
dump_resource "${namespace}" "${bd_kind}" "${bd_name}" "${output_dir}" "" |
|
|
|
if [[ "${bd_kind}" == "VirtualDisk" || "${bd_kind}" == "VirtualImage" ]]; then |
|
local pvc_name=$(kubectl -n "${namespace}" get "${bd_kind}" "${bd_name}" -o json | |
|
jq -cr '.status.target.persistentVolumeClaimName') |
|
|
|
if [[ -n "${pvc_name}" && "${pvc_name}" != "null" ]]; then |
|
dump_resource "${namespace}" "persistentvolumeclaim" "${pvc_name}" "${output_dir}" "pvc-" |
|
|
|
local pv_name=$(kubectl -n "${namespace}" get persistentvolumeclaim "${pvc_name}" -o json | |
|
jq -cr '.spec.volumeName') |
|
[[ -n "${pv_name}" ]] && dump_resource "" "persistentvolume" "${pv_name}" "${output_dir}" "pv-" |
|
fi |
|
fi |
|
} |
|
|
|
dump_pod_data() { |
|
local namespace="${1}" pod_name="${2}" output_dir="${3}" |
|
local prefix="pod-${pod_name}" |
|
output_dir="${output_dir}/${prefix}" |
|
mkdir -p "${output_dir}" |
|
|
|
# Get pod YAML |
|
kubectl -n "${namespace}" get pod "${pod_name}" -o yaml > "${output_dir}/${prefix}.yaml" |
|
echo " Saved to: ${prefix}.yaml" |
|
|
|
# Get pod logs |
|
if kubectl logs -n "${namespace}" "${pod_name}" > "${output_dir}/${prefix}.log"; then |
|
echo " Logs saved to: ${prefix}.log" |
|
fi |
|
|
|
# Get XML files if pod is running |
|
local phase=$(kubectl get pod -n "${namespace}" "${pod_name}" -o json | jq -cr '.status.phase') |
|
if [[ "${phase}" == "Running" ]]; then |
|
local xml_name="${namespace}_${VM_NAME}" |
|
for path in "run/libvirt/qemu" "etc/libvirt/qemu"; do |
|
local xml_file="${output_dir}/$(echo ${path} | cut -d/ -f1)-${xml_name}.xml" |
|
if kubectl cp -n "${namespace}" "${pod_name}:${path}/${xml_name}.xml" "${xml_file}" >/dev/null 2>&1; then |
|
echo " XML saved to: ${xml_file##*/}" |
|
fi |
|
done |
|
fi |
|
} |
|
|
|
create_bundle() { |
|
local namespace="${1}" vm_name="${2}" output_dir="${3}" |
|
mkdir -p "${output_dir}" |
|
|
|
say_green "\nDumping system logs:" |
|
dump_system_logs "${output_dir}" |
|
|
|
say_green "\nDumping core resources:" |
|
dump_resource "${namespace}" "vms" "${vm_name}" "${output_dir}" "vm-" |
|
dump_resource "${namespace}" "internalvirtualizationvirtualmachines" "${vm_name}" "${output_dir}" "kvvm-" |
|
dump_resource "${namespace}" "internalvirtualizationvirtualmachineinstances" "${vm_name}" "${output_dir}" "kvvmi-" |
|
|
|
# Dump pods and related data |
|
say_green "\nDumping pod information:" |
|
local pods=$(kubectl -n "${namespace}" get pods -l "vm.kubevirt.internal.virtualization.deckhouse.io/name=${vm_name}" -o name | cut -d/ -f2) |
|
for pod in ${pods}; do |
|
say_yellow "- Processing pod: ${pod}" |
|
dump_pod_data "${namespace}" "${pod}" "${output_dir}" |
|
done |
|
|
|
# Dump block devices |
|
say_green "\nDumping block devices:" |
|
local vm_json=$(kubectl -n "${namespace}" get vms "${vm_name}" -o json) |
|
|
|
# Static block devices |
|
while IFS= read -r bd; do |
|
[[ -z "${bd}" ]] && continue |
|
dump_block_device "${namespace}" "$(jq -r '.kind' <<< "${bd}")" "$(jq -r '.name' <<< "${bd}")" "${output_dir}" |
|
done <<< "$(jq -cr '.spec.blockDeviceRefs[]' <<< "${vm_json}")" |
|
|
|
# Hotplug block devices |
|
while IFS= read -r bd; do |
|
[[ -z "${bd}" ]] && continue |
|
local bd_kind=$(jq -r '.kind' <<< "${bd}") |
|
local bd_name=$(jq -r '.name' <<< "${bd}") |
|
|
|
dump_block_device "${namespace}" "${bd_kind}" "${bd_name}" "${output_dir}" |
|
|
|
# Dump associated VMBDA |
|
local vmbda_name=$(jq -r '.virtualMachineBlockDeviceAttachmentName' <<< "${bd}") |
|
dump_resource "${namespace}" "vmbda" "${vmbda_name}" "${output_dir}" "" |
|
done <<< "$(jq -cr '.status.blockDeviceRefs[] | select(.hotplugged==true)' <<< "${vm_json}")" |
|
} |
|
|
|
main() { |
|
check_dependencies |
|
parse_args "${@}" |
|
|
|
local timestamp=$(date +"%Y-%m-%d-%H-%M") |
|
local bundle_dir="${BUNDLE_PATH}/v12n-vm-${VM_NAME}-${timestamp}" |
|
local bundle_tar="${bundle_dir}.tar.gz" |
|
|
|
say_magenta "\nStarting bundle creation: ${VM_NAME}" |
|
create_bundle "${NAMESPACE}" "${VM_NAME}" "${bundle_dir}" |
|
|
|
if ((NO_TAR == 0)); then |
|
say_magenta "\nCompressing bundle..." |
|
tar -czf "${bundle_tar}" -C "${bundle_dir%/*}" "${bundle_dir##*/}" |
|
say_magenta "Bundle size: $(du -h "${bundle_tar}" | cut -f1)" |
|
echo "Created: ${bundle_tar}" |
|
fi |
|
|
|
((KEEP_FILES == 0)) && { say_magenta "Cleaning up..."; rm -rf "${bundle_dir}"; } |
|
say_green "\nOperation completed successfully" |
|
} |
|
|
|
main "${@}" |