├── test.sh ├── tests ├── vmdir.bash ├── vmname.bash ├── check_prerequisites.bats ├── check_destroy.bats ├── check_distributions.bats └── check_script.bats ├── .kivrc ├── LICENSE ├── README.md └── kvm-install-vm /test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | $(which bats) tests/ 3 | -------------------------------------------------------------------------------- /tests/vmdir.bash: -------------------------------------------------------------------------------- 1 | VMDIR=${HOME}/virt/vms 2 | if [ -f ~/.kivrc ] 3 | then 4 | source ${HOME}/.kivrc 5 | fi 6 | -------------------------------------------------------------------------------- /tests/vmname.bash: -------------------------------------------------------------------------------- 1 | if [ -z "${VMNAME}" ] 2 | then 3 | TIMESTAMP=$(date '+%Y%m%d%H%M%S') 4 | export VMNAME="batstestvm-${TIMESTAMP}" 5 | fi 6 | -------------------------------------------------------------------------------- /tests/check_prerequisites.bats: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bats 2 | 3 | @test "Check that genisoimage is available" { 4 | command -v genisoimage 5 | } 6 | 7 | @test "Check that virt-install is available" { 8 | command -v virt-install 9 | } 10 | 11 | @test "Check that virt-resize is available" { 12 | command -v virt-resize 13 | } 14 | 15 | @test "Check that qemu-img is available" { 16 | command -v qemu-img 17 | } 18 | 19 | @test "Check that virsh is available" { 20 | command -v virsh 21 | } 22 | -------------------------------------------------------------------------------- /tests/check_destroy.bats: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bats 2 | 3 | VMPREFIX=batstestvm 4 | 5 | @test "Install VM - $VMPREFIX-centos7-destroy" { 6 | run kvm-install-vm create ${VMPREFIX}-centos7-destroy 7 | [ "$status" -eq 0 ] 8 | } 9 | 10 | @test "Shutdown/Destroy VM - $VMPREFIX-centos7-destroy" { 11 | run virsh destroy $VMPREFIX-centos7-destroy 12 | [ "$status" -eq 0 ] 13 | } 14 | 15 | @test "Delete VM - $VMPREFIX-centos7-destroy" { 16 | run kvm-install-vm remove ${VMPREFIX}-centos7-destroy 17 | [[ "${lines[0]}" =~ "Domain is not running" ]] 18 | [ "$status" -eq 0 ] 19 | } 20 | -------------------------------------------------------------------------------- /.kivrc: -------------------------------------------------------------------------------- 1 | ## .kivrc - Set custom defaults for kvm-install-vm 2 | 3 | # Autostart 4 | #AUTOSTART=false 5 | 6 | # Number of virtual CPUs 7 | #CPUS=1 8 | 9 | # Use host cpu features to the guest 10 | #FEATURE=host 11 | 12 | # Amount of RAM in MB 13 | #MEMORY=1024 14 | 15 | # Disk Size in GB 16 | #DISK_SIZE=10 17 | 18 | # DNS domain 19 | #DNSDOMAIN=example.local 20 | 21 | # Graphics type 22 | #GRAPHICS=spice 23 | 24 | # Console port 25 | #PORT=-1 26 | 27 | # Resize disk (boolean) 28 | #RESIZE_DISK=false 29 | 30 | # Directory to store images 31 | #IMAGEDIR=${HOME}/virt/images 32 | 33 | # Directory to store vms 34 | #VMDIR=${HOME}/virt/vms 35 | 36 | # Hypervisor bridge 37 | #BRIDGE=virbr0 38 | 39 | # SSH public key 40 | #PUBKEY=${HOME}/.ssh/id_rsa.pub 41 | 42 | # Distribution 43 | #DISTRO=centos7 44 | 45 | # MAC Address 46 | #MACADDRESS= 47 | 48 | # Timezone 49 | #TIMEZONE=US/Eastern 50 | 51 | # User 52 | #ADDITIONAL_USER=${USER} 53 | 54 | # Verbosity 55 | #VERBOSE=0 56 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Giovanni Torres 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /tests/check_distributions.bats: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bats 2 | 3 | VMNAME=batstestvm 4 | 5 | function create_test_vm () 6 | { 7 | local -r var="$1" 8 | run kvm-install-vm create -t ${var} ${VMNAME}-${var} 9 | [ "$status" -eq 0 ] 10 | } 11 | 12 | function remove_test_vm () 13 | { 14 | local -r var="$1" 15 | run kvm-install-vm remove ${VMNAME}-${var} 16 | [ "$status" -eq 0 ] 17 | } 18 | 19 | @test "Install VM (Amazon Linux 2) - $VMNAME-amazon2" { 20 | create_test_vm amazon2 21 | } 22 | 23 | @test "Delete VM (Amazon Linux 2) - $VMNAME-amazon2" { 24 | remove_test_vm amazon2 25 | } 26 | 27 | @test "Install VM (CentOS 7 Atomic) - $VMNAME-centos7-atomic" { 28 | create_test_vm centos7-atomic 29 | } 30 | 31 | @test "Delete VM (CentOS 7 Atomic) - $VMNAME-centos7-atomic" { 32 | remove_test_vm centos7-atomic 33 | } 34 | 35 | @test "Install VM (Fedora 29) - $VMNAME-fedora29" { 36 | create_test_vm fedora27 37 | } 38 | 39 | @test "Delete VM (Fedora 29) - $VMNAME-fedora29" { 40 | remove_test_vm fedora27 41 | } 42 | 43 | @test "Install VM (Fedora 29 Atomic) - $VMNAME-fedora29-atomic" { 44 | create_test_vm fedora27-atomic 45 | } 46 | 47 | @test "Delete VM (Fedora 29 Atomic) - $VMNAME-fedora29-atomic" { 48 | remove_test_vm fedora27-atomic 49 | } 50 | 51 | @test "Install VM (Fedora 30) - $VMNAME-fedora30" { 52 | create_test_vm fedora28 53 | } 54 | 55 | @test "Delete VM (Fedora 30) - $VMNAME-fedora30" { 56 | remove_test_vm fedora28 57 | } 58 | 59 | @test "Install VM (Ubuntu 16.04) - $VMNAME-ubuntu1604" { 60 | create_test_vm ubuntu1604 61 | } 62 | 63 | @test "Delete VM (Ubuntu 16.04) - $VMNAME-ubuntu1604" { 64 | remove_test_vm ubuntu1604 65 | } 66 | 67 | @test "Install VM (Ubuntu 18.04) - $VMNAME-ubuntu1804" { 68 | create_test_vm ubuntu1804 69 | } 70 | 71 | @test "Delete VM (Ubuntu 18.04) - $VMNAME-ubuntu1804" { 72 | remove_test_vm ubuntu1804 73 | } 74 | 75 | @test "Install VM (Debian 9) - $VMNAME-debian9" { 76 | create_test_vm debian9 77 | } 78 | 79 | @test "Delete VM (Debian 9) - $VMNAME-debian9" { 80 | remove_test_vm debian9 81 | } 82 | -------------------------------------------------------------------------------- /tests/check_script.bats: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bats 2 | 3 | load vmdir 4 | load vmname 5 | 6 | @test "Check for help usage message" { 7 | run kvm-install-vm 8 | [[ "${lines[0]}" =~ "NAME" ]] 9 | } 10 | 11 | @test "Test create with no hostname" { 12 | run kvm-install-vm create 13 | [[ "${lines[0]}" =~ "NAME" ]] 14 | } 15 | 16 | @test "Test create with options and no hostname" { 17 | run kvm-install-vm create -t debian9 -d 20 18 | [[ "${lines[0]}" =~ "Please specify a single host to create." ]] 19 | } 20 | 21 | @test "Test remove with no hostname" { 22 | run kvm-install-vm remove 23 | [[ "${lines[0]}" =~ "NAME" ]] 24 | } 25 | 26 | @test "Install VM (CentOS7) - $VMNAME" { 27 | run kvm-install-vm create $VMNAME 28 | [ "$status" -eq 0 ] 29 | } 30 | 31 | @test "Check running VM state" { 32 | run bash -c "virsh -q domstate $VMNAME" 33 | [ "$output" = "running" ] 34 | } 35 | 36 | @test "Check libvirt-nss hostname resolution" { 37 | run sleep 45 38 | run ping -c 1 $VMNAME 39 | [ "$status" -eq 0 ] 40 | [[ "${lines[-2]}" =~ "1 packets transmitted, 1 received," ]] 41 | } 42 | 43 | @test "Check cloud-init package is removed" { 44 | run ssh -o StrictHostKeyChecking=no centos@$VMNAME rpm -q cloud-init 45 | [[ "$output" =~ "package cloud-init is not installed" ]] 46 | } 47 | 48 | @test "Attach disk to VM without specifying target" { 49 | run bash -c "kvm-install-vm attach-disk -d 1 $VMNAME" 50 | [ "$status" -eq 2 ] 51 | [[ "${lines[0]}" =~ "ERR: You must specify a target device" ]] 52 | } 53 | 54 | @test "Attach disk to VM without specifying disk size" { 55 | run bash -c "kvm-install-vm attach-disk -t vdb $VMNAME" 56 | [ "$status" -eq 2 ] 57 | [[ "${lines[0]}" =~ "You must specify a size" ]] 58 | } 59 | 60 | @test "Attach disk to VM" { 61 | run bash -c "kvm-install-vm attach-disk -d 1 -t vdb $VMNAME" 62 | [ "$status" -eq 0 ] 63 | } 64 | 65 | @test "Check block list for VM" { 66 | run bash -c "grep ^vdb <(virsh domblklist $VMNAME)" 67 | [ "$status" -eq 0 ] 68 | } 69 | 70 | @test "Delete VM - $VMNAME" { 71 | run bash -c "kvm-install-vm remove $VMNAME" 72 | [ "$status" -eq 0 ] 73 | } 74 | 75 | @test "Check destroyed VM state" { 76 | run bash -c "virsh -q domstate $VMNAME" 77 | [[ "$output" =~ "error: failed to get domain '$VMNAME'" ]] 78 | } 79 | 80 | @test "Check destroyed VM files" { 81 | run bash -c "ls ${VMDIR}/${VMNAME}" 82 | [[ "$output" =~ "No such file or directory" ]] 83 | } 84 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## kvm-install-vm 2 | 3 | A bash wrapper around virt-install to build virtual machines on a local KVM 4 | hypervisor. You can run it as a normal user which will use `qemu:///session` to 5 | connect locally to your KVM domains. 6 | 7 | Tested on the latest Fedora. 8 | 9 | ### Prerequisites 10 | 11 | You need to have the KVM hypervisor installed, along with a few other packages: 12 | 13 | - genisoimage or mkisofs 14 | - virt-install 15 | - libguestfs-tools-c 16 | - qemu-img 17 | - libvirt-client 18 | 19 | To install the dependencies, run: 20 | 21 | ``` 22 | sudo dnf -y install genisoimage virt-install libguestfs-tools-c qemu-img libvirt-client wget 23 | ``` 24 | 25 | If you want to resolve guests by their hostnames, install the `libvirt-nss` package: 26 | 27 | ``` 28 | sudo dnf -y install libvirt-nss 29 | ``` 30 | 31 | Then, add `libvirt` and `libvirt_guest` to list of **hosts** databases in 32 | `/etc/nsswitch.conf`. See [here](https://libvirt.org/nss.html) for more 33 | information. 34 | 35 | ### Usage 36 | 37 | ``` 38 | $ kvm-install-vm help 39 | NAME 40 | kvm-install-vm - Install virtual guests using cloud-init on a local KVM 41 | hypervisor. 42 | 43 | SYNOPSIS 44 | kvm-install-vm COMMAND [OPTIONS] 45 | 46 | DESCRIPTION 47 | A bash wrapper around virt-install to build virtual machines on a local KVM 48 | hypervisor. You can run it as a normal user which will use qemu:///session 49 | to connect locally to your KVM domains. 50 | 51 | COMMANDS 52 | help - show this help or help for a subcommand 53 | create - create a new guest domain 54 | list - list all domains, running and stopped 55 | remove - delete a guest domain 56 | ``` 57 | 58 | #### Creating Guest VMs 59 | 60 | ``` 61 | $ kvm-install-vm help create 62 | NAME 63 | kvm-install-vm create [OPTIONS] VMNAME 64 | 65 | DESCRIPTION 66 | Create a new guest domain. 67 | 68 | OPTIONS 69 | -a Autostart (default: false) 70 | -b Bridge (default: virbr0) 71 | -c Number of vCPUs (default: 1) 72 | -d Disk Size (GB) (default: 10) 73 | -D DNS Domain (default: example.local) 74 | -f CPU Model / Feature (default: host) 75 | -g Graphics type (default: spice) 76 | -h Display help 77 | -i Custom QCOW2 Image 78 | -k SSH Public Key (default: $HOME/.ssh/id_rsa.pub) 79 | -l Location of Images (default: $HOME/virt/images) 80 | -L Location of VMs (default: $HOME/virt/vms) 81 | -m Memory Size (MB) (default: 1024) 82 | -M Mac address (default: auto-assigned) 83 | -p Console port (default: auto) 84 | -s Custom shell script 85 | -t Linux Distribution (default: centos7) 86 | -T Timezone (default: US/Eastern) 87 | -u Custom user (defualt: $USER) 88 | -v Be verbose 89 | 90 | DISTRIBUTIONS 91 | NAME DESCRIPTION LOGIN 92 | amazon2 Amazon Linux 2 ec2-user 93 | centos7 CentOS 7 centos 94 | centos7-atomic CentOS 7 Atomic Host centos 95 | centos6 CentOS 6 centos 96 | debian9 Debian 9 (Stretch) debian 97 | fedora27 Fedora 27 fedora 98 | fedora27-atomic Fedora 27 Atomic Host fedora 99 | fedora28 Fedora 28 fedora 100 | fedora28-atomic Fedora 28 Atomic Host fedora 101 | ubuntu1604 Ubuntu 16.04 LTS (Xenial Xerus) ubuntu 102 | ubuntu1804 Ubuntu 18.04 LTS (Bionic Beaver) ubuntu 103 | 104 | EXAMPLES 105 | kvm-install-vm create foo 106 | Create VM with the default parameters: CentOS 7, 1 vCPU, 1GB RAM, 10GB 107 | disk capacity. 108 | 109 | kvm-install-vm create -c 2 -m 2048 -d 20 foo 110 | Create VM with custom parameters: 2 vCPUs, 2GB RAM, and 20GB disk 111 | capacity. 112 | 113 | kvm-install-vm create -t debian9 foo 114 | Create a Debian 9 VM with the default parameters. 115 | 116 | kvm-install-vm create -T UTC foo 117 | Create a default VM with UTC timezone. 118 | 119 | kvm-install-vm create -s ~/script.sh -g vnc -u bar foo 120 | Create a VM with a custom script included in user-data, a graphical 121 | console accessible over VNC, and a user named 'bar'. 122 | ``` 123 | 124 | #### Deleting a Guest Domain 125 | 126 | ``` 127 | $ kvm-install-vm help remove 128 | NAME 129 | kvm-install-vm remove [COMMANDS] VMNAME 130 | 131 | DESCRIPTION 132 | Destroys (stops) and undefines a guest domain. This also remove the 133 | associated storage pool. 134 | 135 | COMMANDS 136 | help - show this help 137 | 138 | EXAMPLE 139 | kvm-install-vm remove foo 140 | Remove (destroy and undefine) a guest domain. WARNING: This will 141 | delete the guest domain and any changes made inside it! 142 | ``` 143 | 144 | #### Attaching a new disk 145 | 146 | ``` 147 | $ kvm-install-vm help attach-disk 148 | NAME 149 | kvm-install-vm attach-disk [OPTIONS] [COMMANDS] VMNAME 150 | 151 | DESCRIPTION 152 | Attaches a new disk to a guest domain. 153 | 154 | COMMANDS 155 | help - show this help 156 | 157 | OPTIONS 158 | -d SIZE Disk size (GB) 159 | -f FORMAT Disk image format (default: qcow2) 160 | -s IMAGE Source of disk device 161 | -t TARGET Disk device target 162 | 163 | EXAMPLE 164 | kvm-install-vm attach-disk -d 10 -s example-5g.qcow2 -t vdb foo 165 | Attach a 10GB disk device named example-5g.qcow2 to the foo guest 166 | domain. 167 | ``` 168 | 169 | ### Setting Custom Defaults 170 | 171 | Copy the `.kivrc` file to your $HOME directory to set custom defaults. This is 172 | convenient if you find yourself repeatedly setting the same options on the 173 | command line, like the distribution or the number of vCPUs. 174 | 175 | Options are evaluated in the following order: 176 | 177 | - Default options set in the script 178 | - Custom options set in `.kivrc` 179 | - Option flags set on the command line 180 | 181 | ### Notes 182 | 183 | 1. This script will download a qcow2 cloud image from the respective 184 | distribution's download site. See script for URLs. 185 | 186 | 2. If using libvirt-nss, keep in mind that DHCP leases take some time to 187 | expire, so if you create a VM, delete it, and recreate another VM with the 188 | same name in a short period of time, there will be two DHCP leases for the 189 | same host and its hostname will likely not resolve until the old lease 190 | expires. 191 | 192 | ### Testing 193 | 194 | Tests are written using [Bats](https://github.com/sstephenson/bats). To 195 | execute the tests, run `./test.sh` in the root directory of the project. 196 | 197 | ### Use Cases 198 | 199 | If you don't need to use Docker or Vagrant, don't want to make changes to a 200 | production machine, or just want to spin up one or more VMs locally to test 201 | things like: 202 | 203 | - high availability 204 | - clustering 205 | - package installs 206 | - preparing for exams 207 | - checking for system defaults 208 | - anything else you would do with a VM 209 | 210 | ...then this wrapper could be useful for you. 211 | -------------------------------------------------------------------------------- /kvm-install-vm: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | # Set program name variable - basename without subshell 5 | prog=${0##*/} 6 | 7 | function usage () 8 | { 9 | cat << EOF 10 | NAME 11 | kvm-install-vm - Install virtual guests using cloud-init on a local KVM 12 | hypervisor. 13 | 14 | SYNOPSIS 15 | $prog COMMAND [OPTIONS] 16 | 17 | DESCRIPTION 18 | A bash wrapper around virt-install to build virtual machines on a local KVM 19 | hypervisor. You can run it as a normal user which will use qemu:///session 20 | to connect locally to your KVM domains. 21 | 22 | COMMANDS 23 | help - show this help or help for a subcommand 24 | attach-disk - create and attach a disk device to guest domain 25 | create - create a new guest domain 26 | detach-disk - detach a disk device from a guest domain 27 | list - list all domains, running and stopped 28 | remove - delete a guest domain 29 | 30 | EOF 31 | exit 0 32 | } 33 | 34 | function usage_subcommand () 35 | { 36 | case "$1" in 37 | create) 38 | printf "NAME\n" 39 | printf " $prog create [COMMANDS] [OPTIONS] VMNAME\n" 40 | printf "\n" 41 | printf "DESCRIPTION\n" 42 | printf " Create a new guest domain.\n" 43 | printf "\n" 44 | printf "COMMANDS\n" 45 | printf " help - show this help\n" 46 | printf "\n" 47 | printf "OPTIONS\n" 48 | printf " -a Autostart (default: false)\n" 49 | printf " -b Bridge (default: virbr0)\n" 50 | printf " -c Number of vCPUs (default: 1)\n" 51 | printf " -d Disk Size (GB) (default: 10)\n" 52 | printf " -D DNS Domain (default: example.local)\n" 53 | printf " -f CPU Model / Feature (default: host)\n" 54 | printf " -g Graphics type (default: spice)\n" 55 | printf " -h Display help\n" 56 | printf " -i Custom QCOW2 Image\n" 57 | printf " -k SSH Public Key (default: $HOME/.ssh/id_rsa.pub)\n" 58 | printf " -l Location of Images (default: $HOME/virt/images)\n" 59 | printf " -L Location of VMs (default: $HOME/virt/vms)\n" 60 | printf " -m Memory Size (MB) (default: 1024)\n" 61 | printf " -M Mac address (default: auto-assigned)\n" 62 | printf " -p Console port (default: auto)\n" 63 | printf " -s Custom shell script\n" 64 | printf " -t Linux Distribution (default: centos7)\n" 65 | printf " -T Timezone (default: US/Eastern)\n" 66 | printf " -u Custom user (default: $USER)\n" 67 | printf " -v Be verbose\n" 68 | printf "\n" 69 | printf "DISTRIBUTIONS\n" 70 | printf " NAME DESCRIPTION LOGIN\n" 71 | printf " amazon2 Amazon Linux 2 ec2-user\n" 72 | printf " centos7 CentOS 7 centos\n" 73 | printf " centos7-atomic CentOS 7 Atomic Host centos\n" 74 | printf " centos6 CentOS 6 centos\n" 75 | printf " debian9 Debian 9 (Stretch) debian\n" 76 | printf " fedora29 Fedora 29 fedora\n" 77 | printf " fedora29-atomic Fedora 29 Atomic Host fedora\n" 78 | printf " fedora30 Fedora 30 fedora\n" 79 | printf " ubuntu1604 Ubuntu 16.04 LTS (Xenial Xerus) ubuntu\n" 80 | printf " ubuntu1804 Ubuntu 18.04 LTS (Bionic Beaver) ubuntu\n" 81 | printf "\n" 82 | printf "EXAMPLES\n" 83 | printf " $prog create foo\n" 84 | printf " Create VM with the default parameters: CentOS 7, 1 vCPU, 1GB RAM, 10GB\n" 85 | printf " disk capacity.\n" 86 | printf "\n" 87 | printf " $prog create -c 2 -m 2048 -d 20 foo\n" 88 | printf " Create VM with custom parameters: 2 vCPUs, 2GB RAM, and 20GB disk\n" 89 | printf " capacity.\n" 90 | printf "\n" 91 | printf " $prog create -t debian9 foo\n" 92 | printf " Create a Debian 9 VM with the default parameters.\n" 93 | printf "\n" 94 | printf " $prog create -T UTC foo\n" 95 | printf " Create a default VM with UTC timezone.\n" 96 | printf "\n" 97 | ;; 98 | remove) 99 | printf "NAME\n" 100 | printf " $prog remove [COMMANDS] VMNAME\n" 101 | printf "\n" 102 | printf "DESCRIPTION\n" 103 | printf " Destroys (stops) and undefines a guest domain. This also remove the\n" 104 | printf " associated storage pool.\n" 105 | printf "\n" 106 | printf "COMMANDS\n" 107 | printf " help - show this help\n" 108 | printf "\n" 109 | printf "EXAMPLE\n" 110 | printf " $prog remove foo\n" 111 | printf " Remove (destroy and undefine) a guest domain. WARNING: This will\n" 112 | printf " delete the guest domain and any changes made inside it!\n" 113 | ;; 114 | attach-disk) 115 | printf "NAME\n" 116 | printf " $prog attach-disk [OPTIONS] [COMMANDS] VMNAME\n" 117 | printf "\n" 118 | printf "DESCRIPTION\n" 119 | printf " Attaches a new disk to a guest domain.\n" 120 | printf "\n" 121 | printf "COMMANDS\n" 122 | printf " help - show this help\n" 123 | printf "\n" 124 | printf "OPTIONS\n" 125 | printf " -d SIZE Disk size (GB)\n" 126 | printf " -f FORMAT Disk image format (default: qcow2)\n" 127 | printf " -s IMAGE Source of disk device\n" 128 | printf " -t TARGET Disk device target\n" 129 | printf "\n" 130 | printf "EXAMPLE\n" 131 | printf " $prog attach-disk -d 10 -s example-5g.qcow2 -t vdb foo\n" 132 | printf " Attach a 10GB disk device named example-5g.qcow2 to the foo guest\n" 133 | printf " domain.\n" 134 | ;; 135 | list) 136 | printf "NAME\n" 137 | printf " $prog list\n" 138 | printf "\n" 139 | printf "DESCRIPTION\n" 140 | printf " Lists all running and stopped guest domains.\n" 141 | ;; 142 | *) 143 | printf "'$subcommand' is not a valid subcommand.\n" 144 | exit 1 145 | ;; 146 | esac 147 | exit 0 148 | } 149 | 150 | # Console output colors 151 | bold() { echo -e "\e[1m$@\e[0m" ; } 152 | red() { echo -e "\e[31m$@\e[0m" ; } 153 | green() { echo -e "\e[32m$@\e[0m" ; } 154 | yellow() { echo -e "\e[33m$@\e[0m" ; } 155 | 156 | die() { red "ERR: $@" >&2 ; exit 2 ; } 157 | silent() { "$@" > /dev/null 2>&1 ; } 158 | output() { echo -e "- $@" ; } 159 | outputn() { echo -en "- $@ ... " ; } 160 | ok() { green "${@:-OK}" ; } 161 | 162 | pushd() { command pushd "$@" >/dev/null ; } 163 | popd() { command popd "$@" >/dev/null ; } 164 | 165 | # Detect OS and set wget parameters 166 | function set_wget () 167 | { 168 | if [ -f /etc/fedora-release ] 169 | then 170 | WGET="wget --quiet --show-progress" 171 | else 172 | WGET="wget" 173 | fi 174 | } 175 | 176 | function check_vmname_set () 177 | { 178 | [ -n "${VMNAME}" ] || die "VMNAME not set." 179 | } 180 | 181 | function delete_vm () 182 | { 183 | check_vmname_set 184 | 185 | if [ "${DOMAIN_EXISTS}" -eq 1 ] 186 | then 187 | outputn "Destroying ${VMNAME} domain" 188 | virsh destroy --graceful ${VMNAME} > /dev/null 2>&1 \ 189 | && ok \ 190 | || yellow "(Domain is not running.)" 191 | 192 | outputn "Undefining ${VMNAME} domain" 193 | virsh undefine --managed-save ${VMNAME} > /dev/null 2>&1 \ 194 | && ok \ 195 | || die "Could not undefine domain." 196 | else 197 | output "Domain ${VMNAME} does not exist" 198 | fi 199 | 200 | [[ -d ${VMDIR}/${VMNAME} ]] && DISKDIR=${VMDIR}/${VMNAME} || DISKDIR=${IMAGEDIR}/${VMNAME} 201 | [ -d $DISKDIR ] \ 202 | && outputn "Deleting ${VMNAME} files" \ 203 | && rm -rf $DISKDIR \ 204 | && ok 205 | 206 | if [ "${STORPOOL_EXISTS}" -eq 1 ] 207 | then 208 | outputn "Destroying ${VMNAME} storage pool" 209 | virsh pool-destroy ${VMNAME} > /dev/null 2>&1 && ok 210 | else 211 | output "Storage pool ${VMNAME} does not exist" 212 | fi 213 | } 214 | 215 | function fetch_images () 216 | { 217 | # Create image directory if it doesn't already exist 218 | mkdir -p ${IMAGEDIR} 219 | 220 | # Set variables based on $DISTRO 221 | # Use the command "osinfo-query os" to get the list of the accepted OS variants. 222 | case "$DISTRO" in 223 | amazon2) 224 | QCOW=amzn2-kvm-2.0.20190313-x86_64.xfs.gpt.qcow2 225 | OS_VARIANT="auto" 226 | IMAGE_URL=https://cdn.amazonlinux.com/os-images/2.0.20190313/kvm 227 | LOGIN_USER=ec2-user 228 | ;; 229 | centos7) 230 | QCOW=CentOS-7-x86_64-GenericCloud.qcow2 231 | OS_VARIANT="centos7.0" 232 | IMAGE_URL=https://cloud.centos.org/centos/7/images 233 | LOGIN_USER=centos 234 | ;; 235 | centos7-atomic) 236 | QCOW=CentOS-Atomic-Host-7-GenericCloud.qcow2 237 | OS_VARIANT="centos7.0" 238 | IMAGE_URL=http://cloud.centos.org/centos/7/atomic/images 239 | LOGIN_USER=centos 240 | ;; 241 | centos6) 242 | QCOW=CentOS-6-x86_64-GenericCloud.qcow2 243 | OS_VARIANT="centos6.9" 244 | IMAGE_URL=https://cloud.centos.org/centos/6/images 245 | LOGIN_USER=centos 246 | ;; 247 | debian8) 248 | # FIXME: Not yet working. 249 | QCOW=debian-8-openstack-amd64.qcow2 250 | OS_VARIANT="debian8" 251 | IMAGE_URL=https://cdimage.debian.org/cdimage/openstack/current-8 252 | LOGIN_USER=debian 253 | ;; 254 | debian9) 255 | QCOW=debian-9-openstack-amd64.qcow2 256 | OS_VARIANT="debian9" 257 | IMAGE_URL=https://cdimage.debian.org/cdimage/openstack/current-9 258 | LOGIN_USER=debian 259 | ;; 260 | fedora29) 261 | QCOW=Fedora-Cloud-Base-29-1.2.x86_64.qcow2 262 | OS_VARIANT="fedora29" 263 | IMAGE_URL=https://download.fedoraproject.org/pub/fedora/linux/releases/29/Cloud/x86_64/images 264 | LOGIN_USER=fedora 265 | ;; 266 | fedora29-atomic) 267 | QCOW=Fedora-AtomicHost-29-20190611.0.x86_64.qcow2 268 | OS_VARIANT="fedora29" 269 | IMAGE_URL=https://download.fedoraproject.org/pub/alt/atomic/stable/Fedora-29-updates-20190611.0/AtomicHost/x86_64/images/ 270 | LOGIN_USER=fedora 271 | ;; 272 | fedora30) 273 | QCOW=Fedora-Cloud-Base-30-1.2.x86_64.qcow2 274 | OS_VARIANT="fedora29" 275 | IMAGE_URL=https://download.fedoraproject.org/pub/fedora/linux/releases/30/Cloud/x86_64/images 276 | LOGIN_USER=fedora 277 | ;; 278 | ubuntu1604) 279 | QCOW=ubuntu-16.04-server-cloudimg-amd64-disk1.img 280 | OS_VARIANT="ubuntu16.04" 281 | IMAGE_URL=https://cloud-images.ubuntu.com/releases/16.04/release 282 | LOGIN_USER=ubuntu 283 | ;; 284 | ubuntu1804) 285 | QCOW=ubuntu-18.04-server-cloudimg-amd64.img 286 | OS_VARIANT="ubuntu18.04" 287 | IMAGE_URL=https://cloud-images.ubuntu.com/releases/18.04/release 288 | LOGIN_USER=ubuntu 289 | ;; 290 | *) 291 | die "${DISTRO} not a supported OS. Run 'kvm-install-vm create help'." 292 | ;; 293 | esac 294 | 295 | IMAGE=${IMAGEDIR}/${QCOW} 296 | 297 | if [ ! -f ${IMAGEDIR}/${QCOW} ] 298 | then 299 | output "Cloud image not found. Downloading" 300 | set_wget 301 | ${WGET} --directory-prefix ${IMAGEDIR} ${IMAGE_URL}/${QCOW} || \ 302 | die "Could not download image." 303 | fi 304 | 305 | } 306 | 307 | function check_ssh_key () 308 | { 309 | local key 310 | if [ -z "${PUBKEY}" ]; then 311 | # Try to find a suitable key file. 312 | for key in ~/.ssh/id_{rsa,dsa,ed25519}.pub; do 313 | if [ -f "$key" ]; then 314 | PUBKEY="$key" 315 | break 316 | fi 317 | done 318 | fi 319 | 320 | if [ ! -f "${PUBKEY}" ] 321 | then 322 | # Check for existence of a pubkey, or else exit with message 323 | die "Please generate an SSH keypair using 'ssh-keygen -t rsa' or \ 324 | specify one with the "-k" flag." 325 | else 326 | # Place contents of $PUBKEY into $KEY 327 | KEY=$(<${PUBKEY}) 328 | fi 329 | } 330 | 331 | function domain_exists () 332 | { 333 | virsh dominfo "${1}" > /dev/null 2>&1 \ 334 | && DOMAIN_EXISTS=1 \ 335 | || DOMAIN_EXISTS=0 336 | } 337 | 338 | function storpool_exists () 339 | { 340 | virsh pool-info "${1}" > /dev/null 2>&1 \ 341 | && STORPOOL_EXISTS=1 \ 342 | || STORPOOL_EXISTS=0 343 | } 344 | 345 | function set_sudo_group () 346 | { 347 | case "${DISTRO}" in 348 | centos?|fedora??|*-atomic|amazon? ) 349 | SUDOGROUP="wheel" 350 | ;; 351 | ubuntu*|debian? ) 352 | SUDOGROUP="sudo" 353 | ;; 354 | *) 355 | die "OS not supported." 356 | ;; 357 | esac 358 | } 359 | 360 | function set_cloud_init_remove () 361 | { 362 | case "${DISTRO}" in 363 | centos6 ) 364 | CLOUDINITDISABLE="chkconfig cloud-init off" 365 | ;; 366 | centos7|amazon?|fedora??|ubuntu*|debian? ) 367 | CLOUDINITDISABLE="systemctl disable cloud-init.service" 368 | ;; 369 | *-atomic) 370 | CLOUDINITDISABLE="/usr/bin/true" 371 | ;; 372 | esac 373 | } 374 | 375 | function set_network_restart_cmd () 376 | { 377 | case "${DISTRO}" in 378 | centos6 ) NETRESTART="service network stop && service network start" ;; 379 | ubuntu*|debian?) NETRESTART="systemctl stop networking && systemctl start networking" ;; 380 | *) NETRESTART="systemctl stop network && systemctl start network" ;; 381 | esac 382 | } 383 | 384 | function check_delete_known_host () 385 | { 386 | output "Checking for ${IP} in known_hosts file" 387 | grep -q ${IP} ${HOME}/.ssh/known_hosts \ 388 | && outputn "Found entry for ${IP}. Removing" \ 389 | && (sed --in-place "/^${IP}/d" ~/.ssh/known_hosts && ok ) \ 390 | || output "No entries found for ${IP}" 391 | } 392 | 393 | function create_vm () 394 | { 395 | # Create image directory if it doesn't already exist 396 | mkdir -p ${VMDIR} 397 | 398 | check_vmname_set 399 | 400 | # Start clean 401 | [ -d "${VMDIR}/${VMNAME}" ] && rm -rf ${VMDIR}/${VMNAME} 402 | mkdir -p ${VMDIR}/${VMNAME} 403 | 404 | pushd ${VMDIR}/${VMNAME} 405 | 406 | # Create log file 407 | touch ${VMNAME}.log 408 | 409 | # cloud-init config: set hostname, remove cloud-init package, 410 | # and add ssh-key 411 | cat > $USER_DATA << _EOF_ 412 | Content-Type: multipart/mixed; boundary="==BOUNDARY==" 413 | MIME-Version: 1.0 414 | --==BOUNDARY== 415 | Content-Type: text/cloud-config; charset="us-ascii" 416 | 417 | #cloud-config 418 | 419 | # Hostname management 420 | preserve_hostname: False 421 | hostname: ${VMNAME} 422 | fqdn: ${VMNAME}.${DNSDOMAIN} 423 | 424 | # Users 425 | users: 426 | - default 427 | - name: ${ADDITIONAL_USER} 428 | groups: ['${SUDOGROUP}'] 429 | shell: /bin/bash 430 | sudo: ALL=(ALL) NOPASSWD:ALL 431 | ssh-authorized-keys: 432 | - ${KEY} 433 | 434 | # Configure where output will go 435 | output: 436 | all: ">> /var/log/cloud-init.log" 437 | 438 | # configure interaction with ssh server 439 | ssh_genkeytypes: ['ed25519', 'rsa'] 440 | 441 | # Install my public ssh key to the first user-defined user configured 442 | # in cloud.cfg in the template (which is centos for CentOS cloud images) 443 | ssh_authorized_keys: 444 | - ${KEY} 445 | 446 | timezone: ${TIMEZONE} 447 | 448 | # Remove cloud-init when finished with it 449 | runcmd: 450 | - ${NETRESTART} 451 | - ${CLOUDINITDISABLE} 452 | _EOF_ 453 | 454 | if [ ! -z "${SCRIPTNAME+x}" ] 455 | then 456 | SCRIPT=$(< $SCRIPTNAME) 457 | cat >> $USER_DATA << _EOF_ 458 | 459 | --==BOUNDARY== 460 | Content-Type: text/x-shellscript; charset="us-ascii" 461 | ${SCRIPT} 462 | 463 | --==BOUNDARY==-- 464 | _EOF_ 465 | else 466 | cat >> $USER_DATA << _EOF_ 467 | 468 | --==BOUNDARY==-- 469 | _EOF_ 470 | fi 471 | 472 | { echo "instance-id: ${VMNAME}"; echo "local-hostname: ${VMNAME}"; } > $META_DATA 473 | 474 | outputn "Copying cloud image ($(basename ${IMAGE}))" 475 | DISK=${VMNAME}.qcow2 476 | cp $IMAGE $DISK && ok 477 | if $RESIZE_DISK 478 | then 479 | outputn "Resizing the disk to $DISK_SIZE" 480 | # Workaround to prevent virt-resize from renumbering partitions and breaking grub 481 | # See https://bugzilla.redhat.com/show_bug.cgi?id=1472039 482 | # Ubuntu will automatically grow the partition to the new size on its first boot 483 | if [[ "$DISTRO" = "ubuntu1804" ]] || [[ "$DISTRO" = "amazon2" ]] 484 | then 485 | qemu-img resize $DISK $DISK_SIZE &>> ${VMNAME}.log \ 486 | && ok \ 487 | || die "Could not resize disk." 488 | else 489 | qemu-img create -f qcow2 \ 490 | -o preallocation=metadata $DISK.new $DISK_SIZE &>> ${VMNAME}.log \ 491 | && virt-resize --quiet --expand /dev/sda1 $DISK $DISK.new &>> ${VMNAME}.log \ 492 | && (mv $DISK.new $DISK && ok) \ 493 | || die "Could not resize disk." 494 | fi 495 | fi 496 | 497 | # Create CD-ROM ISO with cloud-init config 498 | outputn "Generating ISO for cloud-init" 499 | if command -v genisoimage &>/dev/null 500 | then 501 | genisoimage -output $CI_ISO \ 502 | -volid cidata \ 503 | -joliet -r $USER_DATA $META_DATA &>> ${VMNAME}.log \ 504 | && ok \ 505 | || die "Could not generate ISO." 506 | else 507 | mkisofs -o $CI_ISO -V cidata -J -r $USER_DATA $META_DATA &>> ${VMNAME}.log \ 508 | && ok \ 509 | || die "Could not generate ISO." 510 | fi 511 | 512 | if [ "${VERBOSE}" -eq 1 ] 513 | then 514 | output "Creating storage pool with the following command" 515 | printf " virsh pool-create-as \\ \n" 516 | printf " --name ${VMNAME} \\ \n" 517 | printf " --type dir \\ \n" 518 | printf " --target ${VMDIR}/${VMNAME} \n" 519 | else 520 | outputn "Creating storage pool" 521 | fi 522 | 523 | # Create new storage pool for new VM 524 | (virsh pool-create-as \ 525 | --name ${VMNAME} \ 526 | --type dir \ 527 | --target ${VMDIR}/${VMNAME} &>> ${VMNAME}.log && ok) \ 528 | || die "Could not create storage pool." 529 | 530 | # Add custom MAC Address if specified 531 | if [ -z "${MACADDRESS}" ] 532 | then 533 | NETWORK_PARAMS="bridge=${BRIDGE},model=virtio" 534 | else 535 | NETWORK_PARAMS="bridge=${BRIDGE},model=virtio,mac=${MACADDRESS}" 536 | fi 537 | 538 | if [ "${VERBOSE}" -eq 1 ] 539 | then 540 | output "Installing the domain with the following command" 541 | printf " virt-install \\ \n" 542 | printf " --import \\ \n" 543 | printf " --name ${VMNAME} \\ \n" 544 | printf " --memory ${MEMORY} \\ \n" 545 | printf " --vcpus ${CPUS} \\ \n" 546 | printf " --cpu ${FEATURE} \\ \n" 547 | printf " --disk ${DISK},format=qcow2,bus=virtio \\ \n" 548 | printf " --disk ${CI_ISO},device=cdrom \\ \n" 549 | printf " --network ${NETWORK_PARAMS} \\ \n" 550 | printf " --os-type=linux \\ \n" 551 | printf " --os-variant=${OS_VARIANT} \\ \n" 552 | printf " --graphics ${GRAPHICS},port=${PORT},listen=localhost \\ \n" 553 | printf " --noautoconsole \n" 554 | else 555 | outputn "Installing the domain" 556 | fi 557 | 558 | # Call virt-install to import the cloud image and create a new VM 559 | (virt-install --import \ 560 | --name ${VMNAME} \ 561 | --memory ${MEMORY} \ 562 | --vcpus ${CPUS} \ 563 | --cpu ${FEATURE} \ 564 | --disk ${DISK},format=qcow2,bus=virtio \ 565 | --disk ${CI_ISO},device=cdrom \ 566 | --network ${NETWORK_PARAMS} \ 567 | --os-type=linux \ 568 | --os-variant=${OS_VARIANT} \ 569 | --graphics ${GRAPHICS},port=${PORT},listen=localhost \ 570 | --noautoconsole &>> ${VMNAME}.log && ok ) \ 571 | || die "Could not create domain with virt-install." 572 | 573 | virsh dominfo ${VMNAME} &>> ${VMNAME}.log 574 | 575 | # Enable autostart if true 576 | if $AUTOSTART 577 | then 578 | outputn "Enabling autostart" 579 | virsh autostart \ 580 | --domain ${VMNAME} > /dev/null 2>&1 \ 581 | && ok \ 582 | || die "Could not enable autostart." 583 | fi 584 | 585 | # Eject cdrom 586 | virsh change-media ${VMNAME} sda --eject --config &>> ${VMNAME}.log 587 | 588 | # Remove the unnecessary cloud init files 589 | outputn "Cleaning up cloud-init files" 590 | rm $USER_DATA $META_DATA $CI_ISO && ok 591 | 592 | if [ -f "/var/lib/libvirt/dnsmasq/${BRIDGE}.status" ] 593 | then 594 | outputn "Waiting for domain to get an IP address" 595 | MAC=$(virsh dumpxml ${VMNAME} | awk -F\' '/mac address/ {print $2}') 596 | while true 597 | do 598 | IP=$(grep -B1 $MAC /var/lib/libvirt/dnsmasq/$BRIDGE.status | head \ 599 | -n 1 | awk '{print $2}' | sed -e s/\"//g -e s/,//) 600 | if [ "$IP" = "" ] 601 | then 602 | sleep 1 603 | else 604 | ok 605 | break 606 | fi 607 | done 608 | printf "\n" 609 | check_delete_known_host 610 | else 611 | outputn "Bridge looks like a layer 2 bridge, get the domain's IP address from your DHCP server" 612 | IP="" 613 | fi 614 | 615 | printf "\n" 616 | output "SSH to ${VMNAME}: 'ssh ${LOGIN_USER}@${IP}' or 'ssh ${LOGIN_USER}@${VMNAME}'" 617 | CONSOLE=$(virsh domdisplay ${VMNAME}) 618 | # Workaround because VNC port number shown by virsh domdisplay is offset from 5900 619 | if [ "${GRAPHICS}" = 'vnc' ] 620 | then 621 | CONSOLE_NO_PORT=$(echo $CONSOLE | cut -d ':' -f 1,2 -) 622 | CONSOLE_PORT=$(expr 5900 + $(echo $CONSOLE | cut -d ':' -f 3 -)) 623 | output "Console at ${CONSOLE_NO_PORT}:${CONSOLE_PORT}" 624 | else 625 | output "Console at ${CONSOLE}" 626 | fi 627 | output "DONE" 628 | 629 | popd 630 | } 631 | 632 | # Delete VM 633 | function remove () 634 | { 635 | # Parse command line arguments 636 | while getopts ":hv" opt 637 | do 638 | case "$opt" in 639 | v ) VERBOSE=1 ;; 640 | h ) usage ;; 641 | * ) die "Unsupported option. Run 'kvm-install-vm help remove'." ;; 642 | esac 643 | done 644 | 645 | if [ "$#" != 1 ] 646 | then 647 | printf "Please specify a single host to remove.\n" 648 | printf "Run 'kvm-install-vm help remove' for usage.\n" 649 | exit 1 650 | else 651 | VMNAME=$1 652 | fi 653 | 654 | # Check if domain exists and set DOMAIN_EXISTS variable. 655 | domain_exists "${VMNAME}" 656 | 657 | # Check if storage pool exists and set STORPOOL_EXISTS variable. 658 | storpool_exists "${VMNAME}" 659 | 660 | delete_vm "${VMNAME}" 661 | } 662 | 663 | function set_defaults () 664 | { 665 | # Defaults are set here. Override using command line arguments. 666 | AUTOSTART=false # Automatically start VM at boot time 667 | CPUS=1 # Number of virtual CPUs 668 | FEATURE=host # Use host cpu features to the guest 669 | MEMORY=1024 # Amount of RAM in MB 670 | DISK_SIZE="" # Disk Size in GB 671 | DNSDOMAIN=example.local # DNS domain 672 | GRAPHICS=spice # Graphics type 673 | RESIZE_DISK=false # Resize disk (boolean) 674 | IMAGEDIR=${HOME}/virt/images # Directory to store images 675 | VMDIR=${HOME}/virt/vms # Directory to store virtual machines 676 | BRIDGE=virbr0 # Hypervisor bridge 677 | PUBKEY="" # SSH public key 678 | DISTRO=centos7 # Distribution 679 | MACADDRESS="" # MAC Address 680 | PORT=-1 # Console port 681 | TIMEZONE=US/Eastern # Timezone 682 | ADDITIONAL_USER=${USER} # User 683 | VERBOSE=0 # Verbosity 684 | 685 | # Reset OPTIND 686 | OPTIND=1 687 | } 688 | 689 | function set_custom_defaults () 690 | { 691 | # Source custom defaults, if set 692 | if [ -f ~/.kivrc ]; 693 | then 694 | source ${HOME}/.kivrc 695 | fi 696 | } 697 | 698 | function create () 699 | { 700 | # Parse command line arguments 701 | while getopts ":b:c:d:D:f:g:i:k:l:L:m:M:p:s:t:T:u:ahv" opt 702 | do 703 | case "$opt" in 704 | a ) AUTOSTART=${OPTARG} ;; 705 | b ) BRIDGE="${OPTARG}" ;; 706 | c ) CPUS="${OPTARG}" ;; 707 | d ) DISK_SIZE="${OPTARG}" ;; 708 | D ) DNSDOMAIN="${OPTARG}" ;; 709 | f ) FEATURE="${OPTARG}" ;; 710 | g ) GRAPHICS="${OPTARG}" ;; 711 | i ) IMAGE="${OPTARG}" ;; 712 | k ) PUBKEY="${OPTARG}" ;; 713 | l ) IMAGEDIR="${OPTARG}" ;; 714 | L ) VMDIR="${OPTARG}" ;; 715 | m ) MEMORY="${OPTARG}" ;; 716 | M ) MACADDRESS="${OPTARG}" ;; 717 | p ) PORT="${OPTARG}" ;; 718 | s ) SCRIPTNAME="${OPTARG}" ;; 719 | t ) DISTRO="${OPTARG}" ;; 720 | T ) TIMEZONE="${OPTARG}" ;; 721 | u ) ADDITIONAL_USER="${OPTARG}" ;; 722 | v ) VERBOSE=1 ;; 723 | h ) usage ;; 724 | * ) die "Unsupported option. Run 'kvm-install-vm help create'." ;; 725 | esac 726 | done 727 | 728 | shift $((OPTIND - 1)) 729 | 730 | # Resize disk if you specify a disk size either via cmdline option or .kivrc 731 | if [ -n "${DISK_SIZE}" ] 732 | then 733 | RESIZE_DISK=true 734 | DISK_SIZE="${DISK_SIZE}G" # Append 'G' for Gigabyte 735 | fi 736 | 737 | # After all options are processed, make sure only one variable is left (vmname) 738 | if [ "$#" != 1 ] 739 | then 740 | printf "Please specify a single host to create.\n" 741 | printf "Run 'kvm-install-vm help create' for usage.\n" 742 | exit 1 743 | else 744 | VMNAME=$1 745 | fi 746 | 747 | # Set cloud-init variables after VMNAME is assigned 748 | USER_DATA=user-data 749 | META_DATA=meta-data 750 | CI_ISO=${VMNAME}-cidata.iso 751 | 752 | # Check for ssh key 753 | check_ssh_key 754 | 755 | if [ ! -z "${IMAGE+x}" ] 756 | then 757 | output "Using custom QCOW2 image: ${IMAGE}." 758 | OS_VARIANT="auto" 759 | LOGIN_USER="" 760 | else 761 | fetch_images 762 | fi 763 | 764 | # Check if domain already exists 765 | domain_exists "${VMNAME}" 766 | 767 | if [ "${DOMAIN_EXISTS}" -eq 1 ]; then 768 | echo -n "[WARNING] ${VMNAME} already exists. " 769 | read -p "Do you want to overwrite ${VMNAME} [y/N]? " -r 770 | if [[ $REPLY =~ ^[Yy]$ ]]; then 771 | remove ${VMNAME} 772 | else 773 | echo -e "\nNot overwriting ${VMNAME}. Exiting..." 774 | exit 1 775 | fi 776 | fi 777 | 778 | # Set network restart command 779 | set_network_restart_cmd 780 | 781 | # Set cloud init remove command 782 | set_cloud_init_remove 783 | 784 | # Set package manager 785 | set_sudo_group 786 | 787 | # Finally, create requested VM 788 | create_vm 789 | } 790 | 791 | function attach-disk () 792 | { 793 | # Set default variables 794 | FORMAT=qcow2 795 | 796 | # Parse command line arguments 797 | while getopts ":d:f:ps:t:h" opt 798 | do 799 | case "$opt" in 800 | d ) DISKSIZE="${OPTARG}G" ;; 801 | f ) FORMAT="${OPTARG}" ;; 802 | p ) PERSISTENT="${OPTARG}" ;; 803 | s ) SOURCE="${OPTARG}" ;; 804 | t ) TARGET="${OPTARG}" ;; 805 | h ) usage ;; 806 | * ) die "Unsupported option. Run 'kvm-install-vm help attach-disk'." ;; 807 | esac 808 | done 809 | 810 | shift $((OPTIND - 1)) 811 | 812 | [ ! -z ${TARGET} ] || die "You must specify a target device, for e.g. '-t vdb'" 813 | [ ! -z ${DISKSIZE} ] || die "You must specify a size (in GB) for the new device, for e.g. '-d 5'" 814 | 815 | if [ "$#" != 1 ] 816 | then 817 | printf "Please specify a single host to attach a disk to.\n" 818 | printf "Run 'kvm-install-vm help attach-disk' for usage.\n" 819 | exit 1 820 | else 821 | # Set variables 822 | VMNAME=$1 823 | # Directory to create attached disk (Checks both images an vms directories for backward compatibility!) 824 | [[ -d ${VMDIR}/${VMNAME} ]] && DISKDIR=${VMDIR}/${VMNAME} || DISKDIR=${IMAGEDIR}/${VMNAME} 825 | DISKNAME=${VMNAME}-${TARGET}-${DISKSIZE}.${FORMAT} 826 | 827 | if [ ! -f "${DISKDIR}/${DISKNAME}" ] 828 | then 829 | outputn "Creating new '${TARGET}' disk image for domain ${VMNAME}" 830 | (qemu-img create -f ${FORMAT} -o size=$DISKSIZE,preallocation=metadata \ 831 | ${DISKDIR}/${DISKNAME} &>> ${DISKDIR}/${VMNAME}.log && ok ) && \ 832 | 833 | outputn "Attaching ${DISKNAME} to domain ${VMNAME}" 834 | (virsh attach-disk ${VMNAME} \ 835 | --source $DISKDIR/${DISKNAME} \ 836 | --target ${TARGET} \ 837 | --subdriver ${FORMAT} \ 838 | --cache none \ 839 | --persistent &>> ${DISKDIR}/${VMNAME}.log && ok ) \ 840 | || die "Could not attach disk." 841 | else 842 | die "Target ${TARGET} is already created or in use." 843 | fi 844 | 845 | fi 846 | 847 | } 848 | 849 | #-------------------------------------------------- 850 | # Main 851 | #-------------------------------------------------- 852 | 853 | subcommand="${1:-none}" 854 | [[ "${subcommand}" != "none" ]] && shift 855 | 856 | case "${subcommand}" in 857 | none) 858 | usage 859 | ;; 860 | help) 861 | if [[ "${1:-none}" == "none" ]]; then 862 | usage 863 | elif [[ "$1" =~ ^create$|^remove$|^list$|^attach-disk$ ]]; then 864 | usage_subcommand "$1" 865 | else 866 | printf "'$1' is not a valid subcommand.\n\n" 867 | usage 868 | fi 869 | ;; 870 | list) 871 | virsh list --all 872 | exit 0 873 | ;; 874 | create|remove|attach-disk|remove-disk) 875 | if [[ "${1:-none}" == "none" ]]; then 876 | usage_subcommand "${subcommand}" 877 | elif [[ "$1" =~ ^help$ ]]; then 878 | usage_subcommand "${subcommand}" 879 | else 880 | set_defaults 881 | set_custom_defaults 882 | "${subcommand}" "$@" 883 | exit $? 884 | fi 885 | ;; 886 | *) 887 | die "'${subcommand}' is not a valid subcommand. See 'kvm-install-vm help' for a list of subcommands." 888 | ;; 889 | esac 890 | --------------------------------------------------------------------------------