Merge branch 'devel'
Small C application added to begine reducing dependency on virsh, starting by listing machines, their names and their states by directly accessing the libvirt API
This commit is contained in:
commit
ff65da8adb
|
@ -0,0 +1,290 @@
|
|||
#include <libvirt.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
void pr_bad();
|
||||
void pr_conn_bad();
|
||||
void pr_conn_good();
|
||||
void pr_fatal_and_exit(int e);
|
||||
void pr_good();
|
||||
void pr_no_match();
|
||||
|
||||
int get_domain(char *uuid, virDomainPtr *domain);
|
||||
char * get_state_string(virDomainPtr dom, int state);
|
||||
|
||||
void lv_get_name(char *uuid);
|
||||
void lv_get_state(char *uuid);
|
||||
void lv_get_xml(char *uuid);
|
||||
void lv_list_all_name();
|
||||
void lv_list_all_uuid();
|
||||
|
||||
const int E_LIBVIRT=16;
|
||||
virConnectPtr conn;
|
||||
|
||||
/*One of these is displayed on startup. If CONN_BAD, program exits*/
|
||||
const char CONN_BAD[]= "# No Connection";
|
||||
const char CONN_GOOD[]="# Connected";
|
||||
|
||||
/*After a command is issued, one of these displays. Only GOOD is followed
|
||||
by the requested information, then EOF */
|
||||
const char BAD_REQ[]="# Bad Request";
|
||||
const char FATAL[]= "# Fatal error";
|
||||
const char GOOD[]= "# Making API Request";
|
||||
const char NOMATCH[]="# No matching domain";
|
||||
|
||||
/************************************************************************/
|
||||
|
||||
/* Possible commands:
|
||||
* list: list UUIDs of all domains that are defined and/or running
|
||||
* list_name: list names of all domains that are defined and/or running
|
||||
* get_name <uuid>: get name of domain
|
||||
* get_state <uuid>: get state of domain as defined in man qq2clone. It
|
||||
* is possible, though unlikely for this to return an empty string
|
||||
* even when referring to a valid uuid
|
||||
* get_xml <uuid>: print xml file describing domain (prints current
|
||||
* config of a running machine, rather than inactive config)
|
||||
*/
|
||||
|
||||
int main () {
|
||||
setlinebuf(stdout); /* Make sure output is always line buffered */
|
||||
/* Connect to libvirt API */
|
||||
conn=virConnectOpen(NULL);
|
||||
int loop=1;
|
||||
if ( conn == NULL) {
|
||||
pr_conn_bad();
|
||||
exit(E_LIBVIRT);
|
||||
} else {
|
||||
pr_conn_good();
|
||||
}
|
||||
|
||||
char *lineptr, *token, **str_arr;
|
||||
const char delim[]="\t\n ";
|
||||
unsigned int count;
|
||||
int i;
|
||||
size_t n;
|
||||
|
||||
/* Get input from stdin and execute commands in a loop */
|
||||
while(1) {
|
||||
str_arr=NULL;
|
||||
lineptr=NULL; n=0; count=0;
|
||||
|
||||
getline(&lineptr,&n,stdin);
|
||||
token=strtok(lineptr,delim);
|
||||
if (token == NULL) { free(lineptr); pr_bad(); continue; }
|
||||
|
||||
while (token != NULL) {
|
||||
str_arr=realloc(str_arr, sizeof(char*) * (count + 1) );
|
||||
if (str_arr == NULL){ pr_fatal_and_exit(1); }
|
||||
str_arr[count]=token;
|
||||
count++;
|
||||
token=strtok(NULL,delim);
|
||||
}
|
||||
|
||||
if ( count > 2 || count == 0) { free(lineptr); pr_bad(); continue; }
|
||||
if ( strcmp( str_arr[0], "exit" ) == 0 ) {
|
||||
virConnectClose(conn); exit(0);
|
||||
|
||||
} else if (strcmp( str_arr[0], "list" ) == 0 ) {
|
||||
if (count==1){ lv_list_all_uuid(); } else { pr_bad(); }
|
||||
|
||||
} else if (strcmp( str_arr[0], "list_name" ) == 0 ) {
|
||||
if (count==1){ lv_list_all_name(); } else { pr_bad(); }
|
||||
|
||||
} else if (strcmp( str_arr[0], "get_name" ) == 0 ) {
|
||||
if (count==2){ lv_get_name(str_arr[1]); } else { pr_bad(); }
|
||||
|
||||
} else if (strcmp( str_arr[0], "get_state" ) == 0 ) {
|
||||
if (count==2){ lv_get_state(str_arr[1]); } else { pr_bad(); }
|
||||
|
||||
} else if (strcmp( str_arr[0], "get_xml" ) == 0 ) {
|
||||
if (count==2){ lv_get_xml(str_arr[1]); } else { pr_bad(); }
|
||||
|
||||
} else {
|
||||
pr_bad();
|
||||
}
|
||||
|
||||
free(lineptr);
|
||||
if (str_arr != NULL){ free(str_arr); }
|
||||
}
|
||||
|
||||
exit(0);
|
||||
}
|
||||
|
||||
/************************************************************************/
|
||||
|
||||
/***********************************
|
||||
* Basic, repeated output sequences *
|
||||
***********************************/
|
||||
|
||||
void pr_bad() {
|
||||
printf("%s\n",BAD_REQ);
|
||||
}
|
||||
|
||||
void pr_conn_bad() {
|
||||
printf("%s\n", CONN_BAD);
|
||||
}
|
||||
|
||||
void pr_conn_good() {
|
||||
printf("%s\n",CONN_GOOD);
|
||||
}
|
||||
|
||||
/* Okay, this one provides a basic output sequence *and* quits */
|
||||
void pr_fatal_and_exit(int e) {
|
||||
printf("%s\n",FATAL);
|
||||
virConnectClose;
|
||||
exit(e);
|
||||
}
|
||||
|
||||
void pr_good() {
|
||||
printf("%s\n",GOOD);
|
||||
}
|
||||
|
||||
void pr_no_match() {
|
||||
printf("%s\n",NOMATCH);
|
||||
}
|
||||
|
||||
/*****************************************
|
||||
* Helper functions for working with API *
|
||||
*****************************************/
|
||||
|
||||
int get_domain(char *uuid, virDomainPtr *domain){
|
||||
*domain=virDomainLookupByUUIDString(conn, (const char *) uuid);
|
||||
if (domain == NULL){
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
char * get_state_string(virDomainPtr dom, int state)
|
||||
{
|
||||
if (state == VIR_DOMAIN_RUNNING) {
|
||||
return ("running");
|
||||
} else if (state == VIR_DOMAIN_BLOCKED) {
|
||||
return ("idle");
|
||||
} else if ( state == VIR_DOMAIN_PAUSED ) {
|
||||
return ("paused");
|
||||
} else if ( state == VIR_DOMAIN_SHUTDOWN ) {
|
||||
return("shutting-down");
|
||||
} else if ( state == VIR_DOMAIN_SHUTOFF ) {
|
||||
int saved;
|
||||
saved=virDomainHasManagedSaveImage(dom,0);
|
||||
if( saved == 1 ) {
|
||||
return("saved");
|
||||
} else if ( saved == 0 ) {
|
||||
return("off");
|
||||
} else {
|
||||
return(NULL);
|
||||
}
|
||||
} else if ( state == VIR_DOMAIN_CRASHED ) {
|
||||
return("crashed");
|
||||
} else if ( state == VIR_DOMAIN_PMSUSPENDED ) {
|
||||
return ("pmsuspended");
|
||||
} else {
|
||||
return("");
|
||||
}
|
||||
}
|
||||
|
||||
/*******************************************************
|
||||
* Get information from libvirt API and print to stdout *
|
||||
*******************************************************/
|
||||
|
||||
void lv_get_name (char *uuid){
|
||||
virDomainPtr domain;
|
||||
if (get_domain(uuid, &domain) == 0){
|
||||
pr_good();
|
||||
const char *name;
|
||||
name=virDomainGetName(domain);
|
||||
if (name!=NULL){ printf("+%s\n",name); }
|
||||
else { pr_fatal_and_exit(E_LIBVIRT); }
|
||||
virDomainFree(domain);
|
||||
printf("EOF\n");
|
||||
}else{
|
||||
pr_no_match();
|
||||
}
|
||||
}
|
||||
|
||||
void lv_get_state(char *uuid){
|
||||
virDomainPtr domain;
|
||||
if (get_domain(uuid, &domain) == 0){
|
||||
pr_good();
|
||||
int *state=malloc(sizeof(state)), *reason=malloc(sizeof(reason)), ret;
|
||||
ret=virDomainGetState(domain,state,reason,0);
|
||||
if ( ret==0 ){
|
||||
const char *state_str;
|
||||
state_str=get_state_string(domain, *state);
|
||||
printf("+%s\n",state_str);
|
||||
} else { printf("+\n"); }
|
||||
virDomainFree(domain);
|
||||
free(state);
|
||||
free(reason);
|
||||
printf("EOF\n");
|
||||
}else{
|
||||
pr_no_match();
|
||||
}
|
||||
}
|
||||
|
||||
void lv_get_xml(char *uuid){
|
||||
virDomainPtr domain;
|
||||
if (get_domain(uuid, &domain) == 0){
|
||||
pr_good();
|
||||
char *xml;
|
||||
xml=virDomainGetXMLDesc(domain, 0);
|
||||
if ( xml!=NULL ){
|
||||
char *token;
|
||||
token=strtok(xml, "\n");
|
||||
if(token==NULL) { free(xml); virDomainFree(domain); return; }
|
||||
|
||||
while (token != NULL) {
|
||||
printf ("+%s\n",token);
|
||||
token=strtok(NULL, "\n");
|
||||
}
|
||||
free(xml);
|
||||
} else { pr_fatal_and_exit(E_LIBVIRT); }
|
||||
virDomainFree(domain);
|
||||
printf("EOF\n");
|
||||
} else {
|
||||
pr_no_match();
|
||||
}
|
||||
}
|
||||
|
||||
void lv_list_all_uuid() {
|
||||
virDomainPtr *domains;
|
||||
int ret_list, ret_uuid, i;
|
||||
ret_list=virConnectListAllDomains(conn,&domains,0);
|
||||
if(ret_list<0){ virConnectClose(conn);
|
||||
pr_fatal_and_exit(E_LIBVIRT); };
|
||||
pr_good();
|
||||
|
||||
char *uuid=malloc(VIR_UUID_STRING_BUFLEN);
|
||||
for(i=0;i<ret_list;i++){
|
||||
ret_uuid=virDomainGetUUIDString(domains[i],uuid);
|
||||
if (ret_uuid==0) {
|
||||
printf("+%s\n",uuid);
|
||||
}
|
||||
virDomainFree(domains[i]);
|
||||
}
|
||||
|
||||
free(uuid);
|
||||
free(domains);
|
||||
printf("EOF\n");
|
||||
}
|
||||
|
||||
void lv_list_all_name() {
|
||||
virDomainPtr *domains;
|
||||
int ret_list, i;
|
||||
ret_list=virConnectListAllDomains(conn,&domains,0);
|
||||
if(ret_list<0){ virConnectClose(conn); pr_fatal_and_exit(E_LIBVIRT); };
|
||||
pr_good();
|
||||
|
||||
const char *name;
|
||||
for(i=0;i<ret_list;i++){
|
||||
name=virDomainGetName(domains[i]);
|
||||
if (name!=NULL){ printf("+%s\n",name); }
|
||||
else { pr_fatal_and_exit(E_LIBVIRT); }
|
||||
virDomainFree(domains[i]);
|
||||
}
|
||||
|
||||
free(domains);
|
||||
printf("EOF\n");
|
||||
}
|
18
man.pandoc
18
man.pandoc
|
@ -28,7 +28,7 @@ requires:
|
|||
|
||||
Bash 4.0+
|
||||
qemu-img
|
||||
libvirt tools:
|
||||
libvirt tools:
|
||||
virt-clone
|
||||
virt-xml
|
||||
virt-xml-validate
|
||||
|
@ -413,16 +413,12 @@ are involved. This may be due to apparmor, or it may be an issue with
|
|||
libvirt. It is unknown how widespread this issue is, but it is the reason
|
||||
that the default directory storage-qq2clone does not start with '.'
|
||||
|
||||
If the UUID of a clone is changed, qq2clone will no longer be able to
|
||||
track it and will not be able to perform commands on it anymore.
|
||||
If virsh undefine is run on a clone, qq2clone will not be able to see
|
||||
it once it is turned off. This limitation will be eliminated or reduced in
|
||||
the future, when qq2clone moves away from relying on virsh and implements
|
||||
direct usage of the libvirt API. It could be addressed now by using
|
||||
transient domains, but that would require qq2clone to do more things
|
||||
manually instead of just invoking virsh. Since the plan is to
|
||||
transition to a different approach later, that would be wasted effort. For
|
||||
now, if you find yourself in this position just use **qq2clone** check.
|
||||
If the UUID of a clone is changed, qq2clone will no longer be able to
|
||||
track it and will not be able to perform commands on it anymore. This will
|
||||
be addressed in the future using custom metadata in the libvirt domain
|
||||
XML. If the user undefines a domain, this will obviously cause it to
|
||||
disappear from qq2clone's perspective when it is turned off, creating a
|
||||
discrepancy in its database. This can be fixed with **qq2clone** **check**.
|
||||
|
||||
qq2clone can only produce clones by making qcow2 image files. The backing
|
||||
file need not be qcow2, but the images produced by qq2clone always will
|
||||
|
|
488
qq2clone
488
qq2clone
|
@ -1,11 +1,11 @@
|
|||
#!/bin/bash
|
||||
#shellcheck disable=1090 disable=2012
|
||||
|
||||
#-----------------#
|
||||
#@@@@@@@@@@@@@@@@@#
|
||||
#---ERROR CODES---#
|
||||
#@@@@@@@@@@@@@@@@@#
|
||||
#-----------------#
|
||||
#--------------------#
|
||||
#@@@@@@@@@@@@@@@@@@@@#
|
||||
#---LITERAL VALUES---#
|
||||
#@@@@@@@@@@@@@@@@@@@@#
|
||||
#--------------------#
|
||||
|
||||
E_permission=10 # No permission for access or file does not exist
|
||||
E_depends=11 # Lacking required software
|
||||
|
@ -20,6 +20,16 @@ E_timeout=17 # Timeout was exceeded before spice connection to clone
|
|||
E_file=18 # Expected file does not exist or is of wrong type/format
|
||||
E_unexpected=19 # Probably a bug in qq2clone
|
||||
|
||||
# lv_api_do prints one of these when started
|
||||
CONN_BAD="# No Connection"
|
||||
CONN_GOOD="# Connected"
|
||||
|
||||
# lv_api_do prints one of these immediately following a line of input
|
||||
BAD_REQ="# Bad Request"
|
||||
FATAL="# Fatal error"
|
||||
GOOD="# Making API Request"
|
||||
NOMATCH="# No matching domain"
|
||||
|
||||
#---------------------------------------------------#
|
||||
#@@@#@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@#
|
||||
#---NAMED PIPE FOR PASSING DATA BETWEEN FUNCTIONS---#
|
||||
|
@ -55,14 +65,13 @@ TEMPDIR=$(mktemp -d) || temp_error
|
|||
#shellcheck disable=2064
|
||||
trap "exec 3>&-; exec 3<&-;rm -rf $TEMPDIR" EXIT
|
||||
fifo_path="${TEMPDIR}/qq2clone_fifo"
|
||||
mkfifo "$fifo_path" ||
|
||||
{ echo "Cannot make fifo" >&2; exit "$E_extcom" ;}
|
||||
mkfifo "$fifo_path" || fifo_error
|
||||
exec 3<>"$fifo_path"
|
||||
return 0
|
||||
}
|
||||
#=========================================================================#
|
||||
read_pipe ()
|
||||
# DESCRIPTION: Flushes the contents of the named pipe to stdout,
|
||||
# DESCRIPTION: Flushes the contents of the named pipe to stdout,
|
||||
# nonblocking
|
||||
# INPUT: None
|
||||
# OUTPUT: Contents of named pipe on fd3
|
||||
|
@ -75,7 +84,7 @@ read_pipe ()
|
|||
echo "EOF" >&3
|
||||
local line match
|
||||
while IFS= read -r line <&3; do
|
||||
# write_pipe puts a + at the start of every line for read_pipe
|
||||
# write_pipe puts a + at the start of every line
|
||||
if [[ "$line" =~ ^\+(.*)$ ]]; then
|
||||
match="${BASH_REMATCH[1]}"
|
||||
echo "$match"
|
||||
|
@ -94,9 +103,9 @@ write_pipe ()
|
|||
# INPUT: Tell write_pipe whether information is coming on stdin or from
|
||||
# a parameter, then pass information
|
||||
# OUTPUT: None
|
||||
# PARAMETERS: $1: '0' if passing another parameter(s), '1' if writing to
|
||||
# PARAMETERS: $1: '0' if passing another parameter(s), '1' if writing to
|
||||
# stdin instead.
|
||||
# $2 and on: If $1 is 0, this is the information write_pipe will
|
||||
# $2 and on: If $1 is 0, this is the information write_pipe will
|
||||
# write as "$*"
|
||||
#=========================================================================#
|
||||
{
|
||||
|
@ -114,12 +123,84 @@ fi
|
|||
return 0
|
||||
}
|
||||
|
||||
#-------------------#
|
||||
#@@@@@@@@@@@@@@@@@@@#
|
||||
#---USE LV_API_DO---#
|
||||
#@@@@@@@@@@@@@@@@@@@#
|
||||
#-------------------#
|
||||
|
||||
#=========================================================================#
|
||||
lv_api_do_open ()
|
||||
# DESCRIPTION: Open lv_api_do in background
|
||||
# INPUT: None
|
||||
# OUTPUT: Return 0 on success, exit on failure
|
||||
# PARAMETERS: None
|
||||
#=========================================================================#
|
||||
{
|
||||
declare -g lv_api_temp;
|
||||
lv_api_temp="$(mktemp -d )" || temp_error
|
||||
mkfifo "${lv_api_temp}/lv_api_do_fifo" || fifo_error
|
||||
exec 4<>"${lv_api_temp}/lv_api_do_fifo"
|
||||
${QQ2_DIR}/lv_api_do <&4 >&3 2>/dev/null &
|
||||
|
||||
local check
|
||||
read -r check <&3
|
||||
[[ "$check" == "$CONN_BAD" ]] && lv_api_do_bad_conn
|
||||
[[ "$check" == "$CONN_GOOD" ]] || unexpected_error lv_api_do_open
|
||||
|
||||
return 0
|
||||
}
|
||||
#=========================================================================#
|
||||
lv_api_do_comm ()
|
||||
# DESCRIPTION: Issue a command to lv_api_do
|
||||
# INPUT: The command
|
||||
# OUTPUT: Return 0 on success, lv_api_do output can be accessed with
|
||||
# read_pipe. Return 1 on failure. Exit and error message if lv_api_do
|
||||
# encounters a fatal error
|
||||
# PARAMETERS: $@: command string to lv_api_do
|
||||
#=========================================================================#
|
||||
{
|
||||
echo "$*" >&4
|
||||
local check
|
||||
read -r check <&3
|
||||
[[ "$check" == "$BAD_REQ" ]] && unexpected_error lv_api_do_comm
|
||||
[[ "$check" == "$NOMATCH" ]] && return 1
|
||||
[[ "$check" == "$FATAL" ]] &&
|
||||
{ echo "Error using libvirt API" >&2; exit "$E_libvirt"; }
|
||||
[[ "$check" == "$GOOD" ]] || unexpected_error lv_api_do_comm
|
||||
|
||||
# This loop avoids a race condition when trying to read_pipe later by
|
||||
# ensuring that lv_api_do has finished its output before this function
|
||||
# returns
|
||||
local line
|
||||
while read -r line <&3; do
|
||||
[[ "$line" == "EOF" ]] && break
|
||||
write_pipe 0 "${line:1}"
|
||||
done
|
||||
|
||||
return 0
|
||||
}
|
||||
#=========================================================================#
|
||||
lv_api_do_close ()
|
||||
# DESCRIPTION: Tell lv_api_do to exit and close the extra pipe
|
||||
# INPUT: None
|
||||
# OUTPUT: None
|
||||
# PARAMETERS: None
|
||||
#=========================================================================#
|
||||
{
|
||||
echo "exit" >&4
|
||||
exec 4>&-
|
||||
exec 4<&-
|
||||
rm -rf "${lv_api_temp:?}"
|
||||
return 0
|
||||
}
|
||||
|
||||
#-------------------------------------------#
|
||||
#@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@#
|
||||
#---GET/ALTER CONFIGURATION, CHECK SYSTEM---#
|
||||
#@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@#
|
||||
#-------------------------------------------#
|
||||
|
||||
|
||||
#=========================================================================#
|
||||
check_config ()
|
||||
#= DESCRIPTION: Given a name or name/value pair, check if it is a
|
||||
|
@ -144,7 +225,7 @@ def_opt[STORAGE]="^/.*"
|
|||
(( $# == 1 )) &&
|
||||
{ [[ " ${!def_opt[*]} " =~ [[:space:]]${1}[[:space:]] ]];
|
||||
return $?; }
|
||||
|
||||
|
||||
local patt="${def_opt["${1}"]}"
|
||||
[[ -n "$patt" ]] || return 1
|
||||
[[ "$2" =~ $patt ]] || return 1
|
||||
|
@ -187,8 +268,8 @@ done
|
|||
((missing)) &&
|
||||
{ echo "This script won't run until you install the listed software" >&2;
|
||||
exit "$E_depends"; }
|
||||
|
||||
return 0
|
||||
|
||||
return 0
|
||||
}
|
||||
#=========================================================================#
|
||||
disp_conf_names ()
|
||||
|
@ -260,7 +341,7 @@ fi
|
|||
return 0
|
||||
}
|
||||
first_run_setup ()
|
||||
# DESCRIPTION: Generate a new database with default config values,
|
||||
# DESCRIPTION: Generate a new database with default config values,
|
||||
# create subdirectories of QQ2_DIR
|
||||
# INPUT: None
|
||||
# OUTPUT: None
|
||||
|
@ -305,10 +386,10 @@ fi
|
|||
|
||||
sqlite3 <<EOF
|
||||
create table CLONES(uuid TEXT, id INTEGER, template TEXT, disks TEXT);
|
||||
|
||||
|
||||
create table TEMPLATES(name TEXT, md5sum TEXT, disks TEXT,\
|
||||
valid INTEGER);
|
||||
|
||||
|
||||
create table CONFIG(name TEXT, value TEXT);
|
||||
insert into CONFIG values('TEMPLATE_DIR', '${TEMPLATE_DIR}');
|
||||
insert into CONFIG values('USE_SPICE', '${use_spice}');
|
||||
|
@ -337,13 +418,13 @@ get_config ()
|
|||
[[ -e "$QQ2_DIR/sqlite3" ]] ||
|
||||
{ echo "The sqlite3 binary is missing from $QQ2_DIR";
|
||||
exit "$E_depends"; }
|
||||
|
||||
|
||||
local check
|
||||
read -r check \
|
||||
< <(sqlite3 "select exists ( select * from CONFIG)")
|
||||
((check)) ||
|
||||
{ echo "Is the database corrupt? No CONFIG table!" 2>/dev/null;
|
||||
unexpected_error get_config; }
|
||||
exit "$E_config"; }
|
||||
|
||||
declare -gA OPT
|
||||
declare -a opts
|
||||
|
@ -377,6 +458,7 @@ return 0
|
|||
#---USAGE INFORMATION---#
|
||||
#@@@@@@@@@@@@@@@@@@@@@@@#
|
||||
#-----------------------#
|
||||
|
||||
#=========================================================================#
|
||||
usage ()
|
||||
# DESCRIPTION: Output basic usage information
|
||||
|
@ -415,7 +497,7 @@ return 0
|
|||
#@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@#
|
||||
#-----------------------------------#
|
||||
|
||||
|
||||
|
||||
#=========================================================================#
|
||||
prompt_num ()
|
||||
# DESCRIPTION: Prompt user for a number between $1 and $2
|
||||
|
@ -461,7 +543,7 @@ prompt_yes_no ()
|
|||
# OUTPUT: Prompts for input, returns 1 for N/n or 0 for Y/y
|
||||
# PARAMETERS: None
|
||||
#=========================================================================
|
||||
#
|
||||
#
|
||||
{
|
||||
local char
|
||||
until [[ "$char" =~ ^[YyNn]$ ]]; do
|
||||
|
@ -496,8 +578,7 @@ xml="$(virt-xml "$@" <<<"$(read_pipe)" 2>/dev/null)" ||
|
|||
|
||||
write_pipe 1 <<<"$xml"
|
||||
return 0
|
||||
}
|
||||
|
||||
}
|
||||
#=========================================================================#
|
||||
find_tag ()
|
||||
# DESCRIPTION: Use xmllint to do an xpath search of xml and write_pipe
|
||||
|
@ -522,7 +603,7 @@ xmllint --xpath "$1" --auto |& grep -qi 'xpath error' &&
|
|||
|
||||
xmllint --noblanks --dropdtd --nowarning --xpath "$1" \
|
||||
2>/dev/null <(read_pipe) | write_pipe 1
|
||||
|
||||
|
||||
return 0
|
||||
}
|
||||
#=========================================================================#
|
||||
|
@ -911,7 +992,7 @@ if (( ${#CL_MAP[@]} + ${#BAD_CL[@]} )); then
|
|||
read -rs
|
||||
echo ; } >&2
|
||||
fi
|
||||
|
||||
|
||||
local id uuid cl_name
|
||||
while read -r id; do
|
||||
read -r uuid
|
||||
|
@ -967,7 +1048,7 @@ fi
|
|||
#---ERROR MESSAGES AND CHECKS---#
|
||||
#@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@#
|
||||
#-------------------------------#
|
||||
|
||||
|
||||
#=========================================================================#
|
||||
arg_error ()
|
||||
# DESCRIPTION: If args are too few, too many, or simply wrong, this
|
||||
|
@ -1024,10 +1105,10 @@ if [[ "$1" == "-r" ]]; then
|
|||
else
|
||||
redir=0
|
||||
fi
|
||||
|
||||
|
||||
while (($#)); do
|
||||
if { chmod +rw "$1" || { [[ -w "$1" ]] && [[ -r "$1" ]]; } ||
|
||||
readlink "$1" ; } &>/dev/null;
|
||||
if { chmod +rw "$1" || { [[ -w "$1" ]] && [[ -r "$1" ]]; } ||
|
||||
readlink "$1" ; } &>/dev/null;
|
||||
then
|
||||
shift
|
||||
elif [[ -e "$1" ]]; then
|
||||
|
@ -1041,7 +1122,7 @@ then
|
|||
local type line
|
||||
type="$(file -b "$1")"
|
||||
if [[ "$type" =~ directory ]] && ((redir)); then
|
||||
while read -r line; do
|
||||
while read -r line; do
|
||||
check_rw -r "$line"
|
||||
done < <(find "$1" 2>/dev/null)
|
||||
fi
|
||||
|
@ -1147,6 +1228,28 @@ check="$(sqlite3 "select exists ( select * from TEMPLATES where\
|
|||
return 1
|
||||
}
|
||||
#=========================================================================#
|
||||
fifo_error ()
|
||||
# DESCRIPTION: Error to display if fifo creation files
|
||||
# INPUT: None
|
||||
# OUTPUT: Error message and exit code
|
||||
# PARAMETERS: None
|
||||
#=========================================================================#
|
||||
{
|
||||
echo "Cannot make fifo"
|
||||
exit "$E_extcom"
|
||||
} >&2
|
||||
#=========================================================================#
|
||||
lv_api_do_bad_conn ()
|
||||
# DESCRIPTION: Error displayed when lv_api_do cannot connect to API
|
||||
# INPUT: None
|
||||
# OUTPUT: Error message and exit code
|
||||
# PARAMETERS: None
|
||||
#=========================================================================#
|
||||
{
|
||||
echo "Cannot connect to libvirt API"
|
||||
exit "$E_libvirt"
|
||||
} 2>/dev/null
|
||||
#=========================================================================#
|
||||
set_error ()
|
||||
# DESCRIPTION: Used when convert_to_seq fails
|
||||
# INPUT: None
|
||||
|
@ -1399,183 +1502,6 @@ for id in "${!BAD_CL[@]}"; do
|
|||
echo
|
||||
done
|
||||
return 0
|
||||
}
|
||||
#=========================================================================#
|
||||
prompt_delete_orphans ()
|
||||
# DESCRIPTION: Find any image files in qq2clone's default storage directory
|
||||
# that don't belong to qqclone2 or any machine on the current libvirt
|
||||
# connetion
|
||||
# INPUT: None
|
||||
# OUTPUT: Prompts user and gives status updates
|
||||
# PARAMETERS: None
|
||||
#=========================================================================#
|
||||
{
|
||||
# This function is very long, partially due to lots of echo statements
|
||||
# and requests for user input. However, all of the text describing things
|
||||
# to the user also makes it easy to understand the code, so for now
|
||||
# I am leaving it as-is.
|
||||
hr
|
||||
echo
|
||||
echo "qq2clone will look in its default storage pool:"
|
||||
echo
|
||||
echo " ${OPT[STORAGE]}"
|
||||
echo
|
||||
echo "Any files found there that are not storage devices in use by"
|
||||
echo "a virtual machine on the current libvirt connection or qq2clone"
|
||||
echo "templates will be considered orphans. This shouldn't take too long,"
|
||||
echo "but it could if you have a lot of files in that location, a large"
|
||||
echo "number of domains defined, or a slow machine"
|
||||
echo
|
||||
echo "qq2clone will take no action on these files unless directed to"
|
||||
echo
|
||||
echo "Ctrl+C to abort search"
|
||||
echo
|
||||
hr
|
||||
echo
|
||||
local patt
|
||||
patt="^[[:space:]]*[^[:space:]]+[[:space:]]+([^[:space:]].*[^[:space:]])"
|
||||
patt="${patt}[[:space:]]*$"
|
||||
|
||||
local check
|
||||
check="$(virsh list --all)"
|
||||
check="$(strip_ws "$check")"
|
||||
if [[ -n "$check" ]]; then check=1; else check=0; fi
|
||||
|
||||
local uuid device path c=8 n match
|
||||
|
||||
echo "Generating list of domain storage devices..."
|
||||
while (( check )) && read -r device; do
|
||||
while (( c )); do (( c-- )); continue 2; done;
|
||||
|
||||
if [[ -z "$device" ]]; then
|
||||
for ((c=3;c>0;)); do
|
||||
read -r n; [[ -z "$n" ]] && continue
|
||||
(( c-- ))
|
||||
done
|
||||
continue
|
||||
fi
|
||||
[[ "$device" =~ $patt ]] && write_pipe 0 "${BASH_REMATCH[1]}"
|
||||
|
||||
done < <( while read -r uuid; do \
|
||||
echo "domblklist $uuid; echo --err null;\
|
||||
domblklist $uuid --inactive;"; done \
|
||||
< <( virsh list --all --uuid ) | virsh 2>&1 )
|
||||
|
||||
# We have all the disk files associated with domains, but we still need
|
||||
# the ones referenced by template XML
|
||||
declare -a templates
|
||||
while read -r line; do
|
||||
write_pipe 0 "$line"
|
||||
done < <(sqlite3 "select disks from TEMPLATES;")
|
||||
|
||||
echo "Generating list of filepaths in default storage pool..."
|
||||
declare -a f_paths
|
||||
local path
|
||||
while read -r path; do
|
||||
f_paths[${#f_paths[@]}]="$path"
|
||||
done < <(find "${OPT[STORAGE]}" -depth -mindepth 1 2>/dev/null)
|
||||
|
||||
echo "Comparing..."
|
||||
declare -a orphans
|
||||
match=0
|
||||
for path in "${f_paths[@]}"; do
|
||||
while read -r device; do
|
||||
(( match )) && continue
|
||||
if [[ "$device" == "$path" ]]; then
|
||||
match=1
|
||||
fi
|
||||
done < <( read_pipe 1 )
|
||||
[[ "$match" == "1" ]] && { match=0; continue; }
|
||||
orphans[${#orphans[@]}]="$path"
|
||||
done
|
||||
read_pipe >/dev/null
|
||||
|
||||
local j=${#orphans[@]}
|
||||
if ((j)); then
|
||||
echo
|
||||
echo "Total potentially orphaned files: $j"
|
||||
echo
|
||||
echo "Remember that false positives are very possible. qq2clone"
|
||||
echo "considers any file in its default storage pool that is not"
|
||||
echo "a storage device for a virtual machine listed by virsh or"
|
||||
echo "a template known to qq2clone to be an orphan. It is unwise to"
|
||||
echo "delete any detected files without looking at them first"
|
||||
echo
|
||||
prompt_yes_no && less \
|
||||
< <( for path in "${orphans[@]}"; do echo "$path"; done )
|
||||
echo
|
||||
echo "Would you like to store a copy of this list to disk?"
|
||||
local temp
|
||||
if prompt_yes_no; then
|
||||
temp="$(mktemp)"
|
||||
for path in "${orphans[@]}"; do echo "$path"; done > "$temp"
|
||||
echo "File printed to $temp"
|
||||
fi
|
||||
echo
|
||||
echo "1) Delete all files found"
|
||||
echo "2) Answer a prompt to delete or leave alone each file"
|
||||
echo "3) Abort and handle the situation manually"
|
||||
local select prompt=1 file fail=0
|
||||
select="$(prompt_num 1 3)"
|
||||
((select==1)) && prompt=0
|
||||
((select==3)) && { echo "Abort"; return 0; }
|
||||
|
||||
local i
|
||||
for ((i=0;i<j;i++));do
|
||||
hr
|
||||
file="${orphans["$i"]}"
|
||||
echo "$file"
|
||||
echo " $((i+1))/$j"
|
||||
echo " Type: $(file "$file")"
|
||||
if ((prompt)); then
|
||||
echo " 1) Delete it"
|
||||
echo " 2) Leave it alone"
|
||||
echo " 3) Delete all"
|
||||
echo " 4) Abort"
|
||||
select="$(prompt_num 1 4)"
|
||||
((select==2)) && continue
|
||||
((select==3)) && prompt=0
|
||||
((select==4)) && { echo "Abort"; return 0; }
|
||||
fi
|
||||
chmod +rw "$file" &>/dev/null
|
||||
rmdir "$file" &>/dev/null
|
||||
rm -f "$file" &>/dev/null
|
||||
if [[ -e "$file" ]]; then
|
||||
echo "Unable to delete"
|
||||
write_pipe "$file"
|
||||
fail=1
|
||||
else
|
||||
echo "Deleted"
|
||||
fi
|
||||
echo
|
||||
done
|
||||
if ((fail)); then
|
||||
echo "Check complete, but failed to delete some files."
|
||||
echo
|
||||
echo "View a list of fails qq2clone failed to delete?"
|
||||
if prompt_yes_no; then
|
||||
echo
|
||||
read_pipe 1 | less
|
||||
else
|
||||
echo
|
||||
fi
|
||||
echo "Save to disk?"
|
||||
if prompt_yes_no; then
|
||||
echo
|
||||
temp="$(mktemp)"
|
||||
read_pipe > "$temp"
|
||||
echo "File printed to $temp"
|
||||
else
|
||||
echo
|
||||
fi
|
||||
else
|
||||
echo "Orphaned file check completed"
|
||||
fi
|
||||
else
|
||||
echo
|
||||
echo "No orphaned files were found"
|
||||
fi
|
||||
return 0
|
||||
}
|
||||
|
||||
#-----------------------------#
|
||||
|
@ -1583,7 +1509,7 @@ return 0
|
|||
#---INTERACT WITH VIRSH/VMs---#
|
||||
#@@@@@@@@@@@@@@@@@@@@@@@@@@@@@#
|
||||
#-----------------------------#
|
||||
|
||||
|
||||
#=========================================================================#
|
||||
clone ()
|
||||
# DESCRIPTION: Clone a virtual machine from OPT[TEMPLATE]
|
||||
|
@ -1592,7 +1518,7 @@ clone ()
|
|||
# PARAMETERS: $1: (Optional) If '0', create clone intended for staging
|
||||
# changes to a base template image
|
||||
#=========================================================================#
|
||||
{
|
||||
{
|
||||
local base_mach_name line check i
|
||||
local txml="${OPT[TEMPLATE_DIR]}/${OPT[TEMPLATE]}.xml"
|
||||
|
||||
|
@ -1772,7 +1698,7 @@ fi
|
|||
local virsh_out
|
||||
virsh_out="$(virsh undefine "$uuid" "${undef_args[@]}" 2>&1)" ||
|
||||
{ echo "$virsh_out"; unexpected_error delete_machine; }
|
||||
|
||||
|
||||
sqlite3 "delete from CLONES where id='$1' and \
|
||||
template='${OPT[TEMPLATE]}';"
|
||||
|
||||
|
@ -1981,8 +1907,8 @@ read -ra states \
|
|||
local state m
|
||||
if (($1)); then
|
||||
#shellcheck disable=2119
|
||||
# This is only a (functioning) mock implementation meant as a proof of
|
||||
# concept for what XML describing qq2clone's current state may be like.
|
||||
# This is only a (functioning) mock implementation meant as a proof of
|
||||
# concept for what XML describing qq2clone's current state may be like
|
||||
# For this feature to be complete, it would: use a defined format, be
|
||||
# implemented with proper, modular code, and contain all information to
|
||||
# fully define qq2clone's state except for machine images and domain xml.
|
||||
|
@ -2057,33 +1983,34 @@ list_states ()
|
|||
if (($#)); then
|
||||
if [[ "$1" == connect ]]; then
|
||||
echo "all"
|
||||
elif [[ "$1" == destroy ]]; then
|
||||
elif [[ "$1" == destroy ]]; then
|
||||
echo "running idle paused in-shutdown pmsuspended"
|
||||
elif [[ "$1" == exec ]]; then
|
||||
echo "all"
|
||||
elif [[ "$1" == restore ]]; then
|
||||
echo "saved"
|
||||
elif [[ "$1" == resume ]]; then
|
||||
echo "paused"
|
||||
elif [[ "$1" == restore ]]; then
|
||||
echo "saved"
|
||||
elif [[ "$1" == resume ]]; then
|
||||
echo "paused"
|
||||
elif [[ "$1" == rm ]]; then
|
||||
echo "all"
|
||||
elif [[ "$1" == rm-wipe ]]; then
|
||||
echo "all"
|
||||
elif [[ "$1" == rm-shred ]]; then
|
||||
echo "all"
|
||||
elif [[ "$1" == save ]]; then
|
||||
elif [[ "$1" == save ]]; then
|
||||
echo "running pmsuspended idle paused paused"
|
||||
elif [[ "$1" == save-rm ]]; then
|
||||
echo "saved"
|
||||
elif [[ "$1" == start ]]; then
|
||||
elif [[ "$1" == start ]]; then
|
||||
echo "off crashed saved"
|
||||
elif [[ "$1" == suspend ]]; then
|
||||
echo "running pmsuspended idle"
|
||||
elif [[ "$1" == suspend ]]; then
|
||||
echo "running pmsuspended idle"
|
||||
fi
|
||||
else
|
||||
echo -n "all crashed idle in-shutdown off paused pmsuspended running"
|
||||
echo " saved"
|
||||
fi
|
||||
return 0
|
||||
}
|
||||
#=========================================================================#
|
||||
load_template ()
|
||||
|
@ -2094,12 +2021,6 @@ load_template ()
|
|||
# PARAMETERS: None
|
||||
#=========================================================================#
|
||||
{
|
||||
# This is a hacky way of getting the information we need in a reasonably
|
||||
# performant manner. It is a bit fragile, but not overly so assuming that
|
||||
# virsh's output is fairly consistent across versions. This method is
|
||||
# temporary, as later on qq2clone will include portions in (probably) C
|
||||
# that use the libvirt API instead of virsh
|
||||
|
||||
check_template
|
||||
unset BAD_CL CL_MAP CL_STATE NAME_MAP
|
||||
declare -ga BAD_CL CL_MAP CL_STATE NAME_MAP
|
||||
|
@ -2119,30 +2040,14 @@ while read -r id; do
|
|||
uuid_map["$uuid"]="$id"
|
||||
done < <(sqlite3 "select id,uuid from CLONES where template='$t';")
|
||||
|
||||
# To use virsh in shell mode without having to repeatedly invoke it in
|
||||
# different subshells for a large performance penalty, we will run it in
|
||||
# the background and write to it with one fifo while reading it from
|
||||
# another
|
||||
local temp
|
||||
temp="$(mktemp -d)" || temp_error
|
||||
mkfifo "$temp/fifo" &>/dev/null || unexpected_error load_template
|
||||
exec 4<>"$temp/fifo"
|
||||
virsh <&3 >&4 2>&4 &
|
||||
# virsh prepends 5 lines of useless output
|
||||
local c; for ((c=5;c>0;c--)); do read -r <&4; done
|
||||
lv_api_do_open
|
||||
|
||||
local prompt="virsh #" # In the virsh shell, input lines start with this
|
||||
lv_api_do_comm list
|
||||
while read -r uuid; do
|
||||
[[ -n "${uuid_map["$uuid"]}" ]] &&
|
||||
CL_MAP["${uuid_map["$uuid"]}"]="$uuid"
|
||||
done < <(read_pipe);
|
||||
|
||||
echo "list --all --uuid" >&3
|
||||
echo "echo EOF" >&3
|
||||
while read -r uuid <&4; do
|
||||
{ [[ "$uuid" =~ ^$prompt ]] || [[ -z "$uuid" ]] ; } && continue
|
||||
[[ "$uuid" == "EOF" ]] && break
|
||||
|
||||
[[ -n "${uuid_map["$uuid"]}" ]] &&
|
||||
CL_MAP["${uuid_map["$uuid"]}"]="$uuid"
|
||||
done
|
||||
|
||||
local match _uuid
|
||||
for uuid in "${!uuid_map[@]}"; do
|
||||
match=0
|
||||
|
@ -2153,43 +2058,15 @@ for uuid in "${!uuid_map[@]}"; do
|
|||
BAD_CL["${uuid_map["$uuid"]}"]="$uuid"
|
||||
done
|
||||
|
||||
local line
|
||||
local state="" name=""
|
||||
for id in "${!CL_MAP[@]}"; do
|
||||
uuid="${CL_MAP["$id"]}"
|
||||
echo "domstate $uuid" >&3
|
||||
echo "echo EOF" >&3
|
||||
while read -r line <&4; do
|
||||
{ [[ "$line" =~ ^$prompt ]] || [[ -z "$line" ]] ; } && continue
|
||||
[[ "$line" == "EOF" ]] && break
|
||||
[[ "$line" == "in shutdown" ]] && line="in-shutdown"
|
||||
[[ "$line" == "shut off" ]] && line="off"
|
||||
CL_STATE["$id"]="$line"
|
||||
done
|
||||
|
||||
echo "domname $uuid" >&3
|
||||
echo "echo EOF" >&3
|
||||
while read -r line <&4; do
|
||||
{ [[ "$line" =~ ^$prompt ]] || [[ -z "$line" ]] ; } && continue
|
||||
[[ "$line" == "EOF" ]] && break
|
||||
NAME_MAP["$id"]="$line"
|
||||
done
|
||||
lv_api_do_comm get_state "$uuid" && state="$(read_pipe)"
|
||||
CL_STATE["$id"]="$state"
|
||||
lv_api_do_comm get_name "$uuid" && name="$(read_pipe)"
|
||||
NAME_MAP["$id"]="$name"
|
||||
done
|
||||
|
||||
echo "list --all --uuid --with-managed-save" >&3
|
||||
echo "echo EOF" >&3
|
||||
|
||||
while read -r uuid <&4; do
|
||||
{ [[ "$uuid" =~ ^$prompt ]] || [[ -z "$uuid" ]] ; } && continue
|
||||
[[ "$uuid" == "EOF" ]] && break
|
||||
id="${uuid_map["$uuid"]}"
|
||||
[[ -z "$id" ]] && continue; [[ -z "${CL_MAP["$id"]}" ]] && continue;
|
||||
CL_STATE["$id"]="saved"
|
||||
done
|
||||
|
||||
echo "quit" >&3
|
||||
exec 4>&-
|
||||
exec 4<&-
|
||||
rm -rf "$temp" &>/dev/null
|
||||
lv_api_do_close
|
||||
|
||||
return 0
|
||||
}
|
||||
|
@ -2201,7 +2078,7 @@ save_domain ()
|
|||
# PARAMETERS: $1: Machine number
|
||||
#=========================================================================#
|
||||
{
|
||||
local uuid
|
||||
local uuid
|
||||
uuid="${CL_MAP["$1"]}"
|
||||
virsh managedsave "$uuid" &>/dev/null
|
||||
return 0
|
||||
|
@ -2347,9 +2224,9 @@ for ((i=0;i<${#parts[@]};i++)); do
|
|||
else
|
||||
return 1
|
||||
fi
|
||||
|
||||
if ((not)); then
|
||||
minus=" $minus $p "
|
||||
|
||||
if ((not)); then
|
||||
minus="$minus $p"
|
||||
else
|
||||
plus="$plus $p"
|
||||
fi
|
||||
|
@ -2359,7 +2236,7 @@ done
|
|||
local n before=0
|
||||
while read -r n; do
|
||||
[[ -z "$n" ]] && continue
|
||||
[[ "$minus" =~ [[:space:]]${n}[[:space:]] ]] && continue
|
||||
[[ " $minus " =~ [[:space:]]${n}[[:space:]] ]] && continue
|
||||
((before)) && echo -n " "; before=1
|
||||
echo -n "$n"
|
||||
done < <( tr " " "\n" <<<"$plus" | sort -n | uniq )
|
||||
|
@ -2388,10 +2265,7 @@ shift
|
|||
|
||||
local verbose_coms
|
||||
verbose_coms="config|check|list|list-templates|exec|edit|modify-template"
|
||||
if (( OPT[QUIET] == 2)) &&
|
||||
[[ ! "$com" =~ ^($verbose_coms)$
|
||||
]];
|
||||
then
|
||||
if (( OPT[QUIET] == 2)) && [[ ! "$com" =~ ^($verbose_coms)$ ]]; then
|
||||
exec &>/dev/null
|
||||
fi
|
||||
|
||||
|
@ -2536,9 +2410,9 @@ for t in "${templates[@]}"; do
|
|||
fi
|
||||
fi
|
||||
echo
|
||||
|
||||
|
||||
local n sum
|
||||
|
||||
|
||||
sum="$(( ${#BAD_CL[@]} + ${#CL_MAP[@]} ))"
|
||||
echo "TOTAL CLONES: $sum"
|
||||
if (( ${#BAD_CL[@]} )); then
|
||||
|
@ -2557,9 +2431,6 @@ for t in "${templates[@]}"; do
|
|||
echo
|
||||
done
|
||||
|
||||
echo "Do a complete check for potentially orphaned images files now?"
|
||||
prompt_yes_no && { echo; prompt_delete_orphans; }
|
||||
|
||||
exit 0
|
||||
}
|
||||
#=========================================================================#
|
||||
|
@ -2601,7 +2472,7 @@ if ((OPT[QUIET] == 2)); then
|
|||
fi
|
||||
|
||||
(( $# )) || arg_error 0 config
|
||||
|
||||
|
||||
if [[ "$1" == "list" ]]; then
|
||||
[[ -n "$2" ]] && arg_error 1 "config list"
|
||||
disp_conf_names
|
||||
|
@ -2621,8 +2492,8 @@ fi
|
|||
if [[ "$1" == "edit" ]]; then
|
||||
[[ -n "$2" ]] || arg_error 0 "config edit"
|
||||
[[ -n "$4" ]] && arg_error 1 "config edit"
|
||||
check_config "$option" || { echo "Unknown option: $option";
|
||||
exit "$E_args"; }
|
||||
check_config "$option" || { echo "Unknown option: $option";
|
||||
exit "$E_args"; }
|
||||
local line
|
||||
if (($#==3));then
|
||||
line="$3"
|
||||
|
@ -2833,7 +2704,7 @@ exec_com_list ()
|
|||
# qq2clone's overall state
|
||||
#=========================================================================#
|
||||
{
|
||||
(( $# > 1)) && arg_error 1 "list"
|
||||
(( $# > 1)) && arg_error 1 "list"
|
||||
if (($#)); then
|
||||
local line
|
||||
if [[ "$1" == "all" ]]; then
|
||||
|
@ -2846,7 +2717,7 @@ if (($#)); then
|
|||
done < <(get_template_list)
|
||||
elif [[ "$1" == "xml" ]]; then
|
||||
echo "<qq2clone directory=\"${QQ2_DIR}\">"
|
||||
local name value
|
||||
local name value
|
||||
while read -r name; do
|
||||
read -r value
|
||||
echo " <config name=\"$name\" value=\"$value\" />"
|
||||
|
@ -2855,7 +2726,7 @@ if (($#)); then
|
|||
while read -r line; do
|
||||
OPT[TEMPLATE]="$line"
|
||||
load_template
|
||||
list_display 1
|
||||
list_display 1
|
||||
done < <(get_template_list)
|
||||
echo "</qq2clone>"
|
||||
else
|
||||
|
@ -2920,7 +2791,7 @@ if [[ "$2" == "prepare-image" ]]; then
|
|||
((is_staging == 2)) && stage_error
|
||||
((is_staging)) || { clone 0; load_template; }
|
||||
connect 0
|
||||
|
||||
|
||||
elif [[ "$2" == "commit-image" ]]; then
|
||||
((is_staging == 2)) && stage_error
|
||||
if (($#==3)); then
|
||||
|
@ -2929,7 +2800,7 @@ elif [[ "$2" == "commit-image" ]]; then
|
|||
((is_staging)) ||
|
||||
{ echo "No changes are staged" >&2; exit "$E_args"; }
|
||||
commit_image "$@"
|
||||
|
||||
|
||||
elif [[ "$2" == "destroy-image" ]]; then
|
||||
((is_staging == 2)) && stage_error
|
||||
local state uuid
|
||||
|
@ -2938,14 +2809,14 @@ elif [[ "$2" == "destroy-image" ]]; then
|
|||
{ echo "Domain is not running" >&2; exit "$E_args"; }
|
||||
uuid="${CL_MAP[0]}"
|
||||
virsh destroy "$uuid" &>/dev/null
|
||||
|
||||
|
||||
elif [[ "$2" == "discard-image" ]]; then
|
||||
((is_staging == 2)) && stage_error
|
||||
((is_staging)) ||
|
||||
{ echo "No image to discard" >&2; exit "$E_args"; }
|
||||
delete_machine 0 0
|
||||
((OPT[QUIET])) || echo "Image discarded"
|
||||
|
||||
|
||||
elif [[ "$2" == rename ]]; then
|
||||
(( $#==3)) || arg_error 0 "modify-template $1 $2"
|
||||
rename_template "$@"
|
||||
|
@ -2997,6 +2868,7 @@ hr ()
|
|||
#=========================================================================#
|
||||
{
|
||||
echo ----------------------------------------------------------------------
|
||||
return 0
|
||||
}
|
||||
#=========================================================================#
|
||||
parse_flags ()
|
||||
|
|
Loading…
Reference in New Issue