From 7a85b7328e106152173bf17ee67711f8e3323f62 Mon Sep 17 00:00:00 2001 From: sylviamoss Date: Mon, 19 Apr 2021 16:32:04 +0200 Subject: [PATCH] vendor qemu plugin --- command/vendored_plugins.go | 2 + go.mod | 3 +- go.sum | 7 +- .../digitalocean/go-qemu/qmp/socket.go | 43 +- .../digitalocean/go-qemu/qmp/socket_unix.go | 27 + .../go-qemu/qmp/socket_windows.go | 25 + .../hashicorp/packer-plugin-qemu/LICENSE | 373 ++++++++++ .../builder/qemu/artifact.go | 38 ++ .../builder/qemu/builder.go | 229 +++++++ .../builder/qemu/comm_config.go | 73 ++ .../packer-plugin-qemu/builder/qemu/config.go | 642 ++++++++++++++++++ .../builder/qemu/config.hcl2spec.go | 298 ++++++++ .../packer-plugin-qemu/builder/qemu/driver.go | 247 +++++++ .../builder/qemu/driver_mock.go | 74 ++ .../packer-plugin-qemu/builder/qemu/qmp.go | 135 ++++ .../packer-plugin-qemu/builder/qemu/ssh.go | 30 + .../builder/qemu/step_configure_qmp.go | 92 +++ .../builder/qemu/step_configure_vnc.go | 89 +++ .../builder/qemu/step_convert_disk.go | 104 +++ .../builder/qemu/step_copy_disk.go | 78 +++ .../builder/qemu/step_create_disk.go | 91 +++ .../builder/qemu/step_http_ip_discover.go | 68 ++ .../builder/qemu/step_port_forward.go | 74 ++ .../builder/qemu/step_prepare_output_dir.go | 51 ++ .../builder/qemu/step_resize_disk.go | 60 ++ .../builder/qemu/step_run.go | 400 +++++++++++ .../builder/qemu/step_set_iso.go | 54 ++ .../builder/qemu/step_shutdown.go | 94 +++ .../builder/qemu/step_type_boot_command.go | 139 ++++ .../builder/qemu/step_wait_guest_address.go | 136 ++++ vendor/modules.txt | 7 +- 31 files changed, 3773 insertions(+), 10 deletions(-) create mode 100644 vendor/github.com/digitalocean/go-qemu/qmp/socket_unix.go create mode 100644 vendor/github.com/digitalocean/go-qemu/qmp/socket_windows.go create mode 100644 vendor/github.com/hashicorp/packer-plugin-qemu/LICENSE create mode 100644 vendor/github.com/hashicorp/packer-plugin-qemu/builder/qemu/artifact.go create mode 100644 vendor/github.com/hashicorp/packer-plugin-qemu/builder/qemu/builder.go create mode 100644 vendor/github.com/hashicorp/packer-plugin-qemu/builder/qemu/comm_config.go create mode 100644 vendor/github.com/hashicorp/packer-plugin-qemu/builder/qemu/config.go create mode 100644 vendor/github.com/hashicorp/packer-plugin-qemu/builder/qemu/config.hcl2spec.go create mode 100644 vendor/github.com/hashicorp/packer-plugin-qemu/builder/qemu/driver.go create mode 100644 vendor/github.com/hashicorp/packer-plugin-qemu/builder/qemu/driver_mock.go create mode 100644 vendor/github.com/hashicorp/packer-plugin-qemu/builder/qemu/qmp.go create mode 100644 vendor/github.com/hashicorp/packer-plugin-qemu/builder/qemu/ssh.go create mode 100644 vendor/github.com/hashicorp/packer-plugin-qemu/builder/qemu/step_configure_qmp.go create mode 100644 vendor/github.com/hashicorp/packer-plugin-qemu/builder/qemu/step_configure_vnc.go create mode 100644 vendor/github.com/hashicorp/packer-plugin-qemu/builder/qemu/step_convert_disk.go create mode 100644 vendor/github.com/hashicorp/packer-plugin-qemu/builder/qemu/step_copy_disk.go create mode 100644 vendor/github.com/hashicorp/packer-plugin-qemu/builder/qemu/step_create_disk.go create mode 100644 vendor/github.com/hashicorp/packer-plugin-qemu/builder/qemu/step_http_ip_discover.go create mode 100644 vendor/github.com/hashicorp/packer-plugin-qemu/builder/qemu/step_port_forward.go create mode 100644 vendor/github.com/hashicorp/packer-plugin-qemu/builder/qemu/step_prepare_output_dir.go create mode 100644 vendor/github.com/hashicorp/packer-plugin-qemu/builder/qemu/step_resize_disk.go create mode 100644 vendor/github.com/hashicorp/packer-plugin-qemu/builder/qemu/step_run.go create mode 100644 vendor/github.com/hashicorp/packer-plugin-qemu/builder/qemu/step_set_iso.go create mode 100644 vendor/github.com/hashicorp/packer-plugin-qemu/builder/qemu/step_shutdown.go create mode 100644 vendor/github.com/hashicorp/packer-plugin-qemu/builder/qemu/step_type_boot_command.go create mode 100644 vendor/github.com/hashicorp/packer-plugin-qemu/builder/qemu/step_wait_guest_address.go diff --git a/command/vendored_plugins.go b/command/vendored_plugins.go index 12fecfe3f..c1936e19c 100644 --- a/command/vendored_plugins.go +++ b/command/vendored_plugins.go @@ -25,6 +25,7 @@ import ( googlecomputebuilder "github.com/hashicorp/packer-plugin-googlecompute/builder/googlecompute" googlecomputeexportpostprocessor "github.com/hashicorp/packer-plugin-googlecompute/post-processor/googlecompute-export" googlecomputeimportpostprocessor "github.com/hashicorp/packer-plugin-googlecompute/post-processor/googlecompute-import" + qemubuilder "github.com/hashicorp/packer-plugin-qemu/builder/qemu" virtualboxisobuilder "github.com/hashicorp/packer-plugin-virtualbox/builder/virtualbox/iso" virtualboxovfbuilder "github.com/hashicorp/packer-plugin-virtualbox/builder/virtualbox/ovf" virtualboxvmbuilder "github.com/hashicorp/packer-plugin-virtualbox/builder/virtualbox/vm" @@ -53,6 +54,7 @@ var VendoredBuilders = map[string]packersdk.Builder{ "amazon-ebsvolume": new(amazonebsvolumebuilder.Builder), "amazon-instance": new(amazoninstancebuilder.Builder), "googlecompute": new(googlecomputebuilder.Builder), + "qemu": new(qemubuilder.Builder), "vsphere-clone": new(vsphereclonebuilder.Builder), "vsphere-iso": new(vsphereisobuilder.Builder), "virtualbox-iso": new(virtualboxisobuilder.Builder), diff --git a/go.mod b/go.mod index 0428e28fe..70fc0d6d7 100644 --- a/go.mod +++ b/go.mod @@ -22,7 +22,6 @@ require ( github.com/cheggaaa/pb v1.0.27 github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e github.com/dgrijalva/jwt-go v3.2.0+incompatible - github.com/digitalocean/go-qemu v0.0.0-20201211181942-d361e7b4965f github.com/digitalocean/godo v1.11.1 github.com/dsnet/compress v0.0.1 github.com/exoscale/packer-plugin-exoscale v0.1.1 @@ -50,6 +49,7 @@ require ( github.com/hashicorp/packer-plugin-ansible v0.0.2 github.com/hashicorp/packer-plugin-docker v0.0.7 github.com/hashicorp/packer-plugin-googlecompute v0.0.1 + github.com/hashicorp/packer-plugin-qemu v0.0.0-20210419131938-9ec808d0c364 github.com/hashicorp/packer-plugin-sdk v0.2.0 github.com/hashicorp/packer-plugin-virtualbox v0.0.1 github.com/hashicorp/packer-plugin-vmware v0.0.1 @@ -64,7 +64,6 @@ require ( github.com/mattn/go-tty v0.0.0-20191112051231-74040eebce08 github.com/mitchellh/cli v1.1.0 github.com/mitchellh/go-homedir v1.1.0 - github.com/mitchellh/go-vnc v0.0.0-20150629162542-723ed9867aed github.com/mitchellh/mapstructure v1.4.0 github.com/mitchellh/panicwrap v1.0.0 github.com/mitchellh/prefixedio v0.0.0-20151214002211-6e6954073784 diff --git a/go.sum b/go.sum index 0b1f883e5..e8a7f6920 100644 --- a/go.sum +++ b/go.sum @@ -199,8 +199,9 @@ github.com/digitalocean/go-libvirt v0.0.0-20210108193637-3a8ae49ba8cd/go.mod h1: github.com/digitalocean/go-libvirt v0.0.0-20210112203132-25518eb2c840 h1:F3RVNV8SLLNhkNFcbDTgD3wAPMcrMJW6xjjI0JXy9z8= github.com/digitalocean/go-libvirt v0.0.0-20210112203132-25518eb2c840/go.mod h1:gtar3MgGsIO64GgphCHw1cbyxSI6qEuTIm9+izMmlfk= github.com/digitalocean/go-qemu v0.0.0-20181112162955-dd7bb9c771b8/go.mod h1:/YnlngP1PARC0SKAZx6kaAEMOp8bNTQGqS+Ka3MctNI= -github.com/digitalocean/go-qemu v0.0.0-20201211181942-d361e7b4965f h1:BYkBJhHxUJJn27mhqfqWycWaEOWv9JQqLgQ2pOFJMqE= github.com/digitalocean/go-qemu v0.0.0-20201211181942-d361e7b4965f/go.mod h1:y4Eq3ZfZQFWQwVyW0qvgo5seXUIq2C7BlHsdE+xtXL4= +github.com/digitalocean/go-qemu v0.0.0-20210326154740-ac9e0b687001 h1:WAg57gnaAWWjMAELcwHjc2xy0PoXQ5G+vn3+XS6s1jI= +github.com/digitalocean/go-qemu v0.0.0-20210326154740-ac9e0b687001/go.mod h1:IetBE52JfFxK46p2n2Rqm+p5Gx1gpu2hRHsrbnPOWZQ= github.com/digitalocean/godo v1.11.1 h1:OsTh37YFKk+g6DnAOrkXJ9oDArTkRx5UTkBJ2EWAO38= github.com/digitalocean/godo v1.11.1/go.mod h1:h6faOIcZ8lWIwNQ+DN7b3CgX4Kwby5T+nbpNqkUIozU= github.com/dimchansky/utfbom v1.1.0 h1:FcM3g+nofKgUteL8dm/UpdRXNC9KmADgTpLKsu0TRo4= @@ -461,6 +462,8 @@ github.com/hashicorp/packer-plugin-docker v0.0.7 h1:hMTrH7vrkFIjphtbbtpuzffTzSjM github.com/hashicorp/packer-plugin-docker v0.0.7/go.mod h1:IpeKlwOSy2kdgQcysqd3gCsoqjME9jtmpFoKxn7RRNI= github.com/hashicorp/packer-plugin-googlecompute v0.0.1 h1:Shjio88MraB+ocj0VI5+M65r4UBKbYI4eCqLNyPXKEo= github.com/hashicorp/packer-plugin-googlecompute v0.0.1/go.mod h1:MfV898IrEMpKH6wVnvOI5Tkhxm2snf3QxwVqV4k3bNI= +github.com/hashicorp/packer-plugin-qemu v0.0.0-20210419131938-9ec808d0c364 h1:9vTNg4xYdp+PzBFSi+G2Nfc8qw7X1ebrBFKt4wmvARk= +github.com/hashicorp/packer-plugin-qemu v0.0.0-20210419131938-9ec808d0c364/go.mod h1:8Q/LCjO7oplLcLe1KLdEt7rq94h42Di6Lab2DTLNwVg= github.com/hashicorp/packer-plugin-sdk v0.0.6/go.mod h1:Nvh28f+Jmpp2rcaN79bULTouNkGNDRfHckhHKTAXtyU= github.com/hashicorp/packer-plugin-sdk v0.0.7-0.20210111224258-fd30ebb797f0/go.mod h1:YdWTt5w6cYfaQG7IOi5iorL+3SXnz8hI0gJCi8Db/LI= github.com/hashicorp/packer-plugin-sdk v0.0.7-0.20210120105339-f6fd68d2570a/go.mod h1:exN0C+Pe+3zu18l4nxueNjX5cfmslxUX/m/xk4IVmZQ= @@ -478,8 +481,6 @@ github.com/hashicorp/packer-plugin-sdk v0.2.0 h1:A4Dq7p4y1vscY4gMzp7GQaXyDJYYhP4 github.com/hashicorp/packer-plugin-sdk v0.2.0/go.mod h1:0DiOMEBldmB0HEhp0npFSSygC8bIvW43pphEgWkp2WU= github.com/hashicorp/packer-plugin-virtualbox v0.0.1 h1:vTfy7a10RUVMdNnDLo0EQrCVbAG4rGWkaDTMC7MVBi4= github.com/hashicorp/packer-plugin-virtualbox v0.0.1/go.mod h1:OOGNMK8Y8zjsYngesZH5kCbH0Fj8PKvhqPp8w1ejM3Y= -github.com/hashicorp/packer-plugin-vmware v0.0.0-20210416150724-592c0637562a h1:LpZ+8Y1vozsI4vuAjUpUOEzFl+jNSY4SL1kfGkVMmb8= -github.com/hashicorp/packer-plugin-vmware v0.0.0-20210416150724-592c0637562a/go.mod h1:NsiT4IOeDKf/aszQNX+/B1xHrfBR3RdUM3sSqANgNec= github.com/hashicorp/packer-plugin-vmware v0.0.1 h1:jRQAdjHwg3zeCBb52KoZsuxugrHcQhjgQln72o9eGgM= github.com/hashicorp/packer-plugin-vmware v0.0.1/go.mod h1:NsiT4IOeDKf/aszQNX+/B1xHrfBR3RdUM3sSqANgNec= github.com/hashicorp/packer-plugin-vsphere v0.0.1 h1:4SUmRP+mGpBJHp6dLL4dmBCC+yDseTktb9YNLj11mVI= diff --git a/vendor/github.com/digitalocean/go-qemu/qmp/socket.go b/vendor/github.com/digitalocean/go-qemu/qmp/socket.go index a552fbc25..541a88676 100644 --- a/vendor/github.com/digitalocean/go-qemu/qmp/socket.go +++ b/vendor/github.com/digitalocean/go-qemu/qmp/socket.go @@ -18,8 +18,10 @@ import ( "bufio" "context" "encoding/json" + "fmt" "io" "net" + "os" "sync" "sync/atomic" "time" @@ -34,6 +36,9 @@ type SocketMonitor struct { // QEMU version reported by a connected monitor socket. Version *Version + // QEMU QMP capabiltiies reported by a connected monitor socket. + Capabilities []string + // Underlying connection c net.Conn @@ -119,6 +124,7 @@ func (mon *SocketMonitor) Connect() error { return err } mon.Version = &ban.QMP.Version + mon.Capabilities = ban.QMP.Capabilities // Issue capabilities handshake cmd := Command{Execute: qmpCapabilities} @@ -194,13 +200,45 @@ func (mon *SocketMonitor) listen(r io.Reader, events chan<- Event, stream chan<- // For a list of available QAPI commands, see: // http://git.qemu.org/?p=qemu.git;a=blob;f=qapi-schema.json;hb=HEAD func (mon *SocketMonitor) Run(command []byte) ([]byte, error) { + // Just call RunWithFile with no file + return mon.RunWithFile(command, nil) +} + +// RunWithFile behaves like Run but allows for passing a file through out-of-band data. +func (mon *SocketMonitor) RunWithFile(command []byte, file *os.File) ([]byte, error) { // Only allow a single command to be run at a time to ensure that responses // to a command cannot be mixed with responses from another command mon.mu.Lock() defer mon.mu.Unlock() - if _, err := mon.c.Write(command); err != nil { - return nil, err + if file == nil { + // Just send a normal command through. + if _, err := mon.c.Write(command); err != nil { + return nil, err + } + } else { + unixConn, ok := mon.c.(*net.UnixConn) + if !ok { + return nil, fmt.Errorf("RunWithFile only works with unix monitor sockets") + } + + oobSupported := false + for _, capability := range mon.Capabilities { + if capability == "oob" { + oobSupported = true + break + } + } + + if !oobSupported { + return nil, fmt.Errorf("The QEMU server doesn't support oob (needed for RunWithFile)") + } + + // Send the command along with the file descriptor. + oob := getUnixRights(file) + if _, _, err := unixConn.WriteMsgUnix(command, oob, nil); err != nil { + return nil, err + } } // Wait for a response or error to our command @@ -224,6 +262,7 @@ func (mon *SocketMonitor) Run(command []byte) ([]byte, error) { // banner is a wrapper type around a Version. type banner struct { QMP struct { + Capabilities []string `json:"capabilities"` Version Version `json:"version"` } `json:"QMP"` } diff --git a/vendor/github.com/digitalocean/go-qemu/qmp/socket_unix.go b/vendor/github.com/digitalocean/go-qemu/qmp/socket_unix.go new file mode 100644 index 000000000..859ff64ea --- /dev/null +++ b/vendor/github.com/digitalocean/go-qemu/qmp/socket_unix.go @@ -0,0 +1,27 @@ +// Copyright 2016 The go-qemu Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// +build !windows + +package qmp + +import ( + "os" + + "golang.org/x/sys/unix" +) + +func getUnixRights(file *os.File) []byte { + return unix.UnixRights(int(file.Fd())) +} diff --git a/vendor/github.com/digitalocean/go-qemu/qmp/socket_windows.go b/vendor/github.com/digitalocean/go-qemu/qmp/socket_windows.go new file mode 100644 index 000000000..b8a611dbf --- /dev/null +++ b/vendor/github.com/digitalocean/go-qemu/qmp/socket_windows.go @@ -0,0 +1,25 @@ +// Copyright 2016 The go-qemu Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// +build windows + +package qmp + +import ( + "os" +) + +func getUnixRights(file *os.File) []byte { + return nil +} diff --git a/vendor/github.com/hashicorp/packer-plugin-qemu/LICENSE b/vendor/github.com/hashicorp/packer-plugin-qemu/LICENSE new file mode 100644 index 000000000..a612ad981 --- /dev/null +++ b/vendor/github.com/hashicorp/packer-plugin-qemu/LICENSE @@ -0,0 +1,373 @@ +Mozilla Public License Version 2.0 +================================== + +1. Definitions +-------------- + +1.1. "Contributor" + means each individual or legal entity that creates, contributes to + the creation of, or owns Covered Software. + +1.2. "Contributor Version" + means the combination of the Contributions of others (if any) used + by a Contributor and that particular Contributor's Contribution. + +1.3. "Contribution" + means Covered Software of a particular Contributor. + +1.4. "Covered Software" + means Source Code Form to which the initial Contributor has attached + the notice in Exhibit A, the Executable Form of such Source Code + Form, and Modifications of such Source Code Form, in each case + including portions thereof. + +1.5. "Incompatible With Secondary Licenses" + means + + (a) that the initial Contributor has attached the notice described + in Exhibit B to the Covered Software; or + + (b) that the Covered Software was made available under the terms of + version 1.1 or earlier of the License, but not also under the + terms of a Secondary License. + +1.6. "Executable Form" + means any form of the work other than Source Code Form. + +1.7. "Larger Work" + means a work that combines Covered Software with other material, in + a separate file or files, that is not Covered Software. + +1.8. "License" + means this document. + +1.9. "Licensable" + means having the right to grant, to the maximum extent possible, + whether at the time of the initial grant or subsequently, any and + all of the rights conveyed by this License. + +1.10. "Modifications" + means any of the following: + + (a) any file in Source Code Form that results from an addition to, + deletion from, or modification of the contents of Covered + Software; or + + (b) any new file in Source Code Form that contains any Covered + Software. + +1.11. "Patent Claims" of a Contributor + means any patent claim(s), including without limitation, method, + process, and apparatus claims, in any patent Licensable by such + Contributor that would be infringed, but for the grant of the + License, by the making, using, selling, offering for sale, having + made, import, or transfer of either its Contributions or its + Contributor Version. + +1.12. "Secondary License" + means either the GNU General Public License, Version 2.0, the GNU + Lesser General Public License, Version 2.1, the GNU Affero General + Public License, Version 3.0, or any later versions of those + licenses. + +1.13. "Source Code Form" + means the form of the work preferred for making modifications. + +1.14. "You" (or "Your") + means an individual or a legal entity exercising rights under this + License. For legal entities, "You" includes any entity that + controls, is controlled by, or is under common control with You. For + purposes of this definition, "control" means (a) the power, direct + or indirect, to cause the direction or management of such entity, + whether by contract or otherwise, or (b) ownership of more than + fifty percent (50%) of the outstanding shares or beneficial + ownership of such entity. + +2. License Grants and Conditions +-------------------------------- + +2.1. Grants + +Each Contributor hereby grants You a world-wide, royalty-free, +non-exclusive license: + +(a) under intellectual property rights (other than patent or trademark) + Licensable by such Contributor to use, reproduce, make available, + modify, display, perform, distribute, and otherwise exploit its + Contributions, either on an unmodified basis, with Modifications, or + as part of a Larger Work; and + +(b) under Patent Claims of such Contributor to make, use, sell, offer + for sale, have made, import, and otherwise transfer either its + Contributions or its Contributor Version. + +2.2. Effective Date + +The licenses granted in Section 2.1 with respect to any Contribution +become effective for each Contribution on the date the Contributor first +distributes such Contribution. + +2.3. Limitations on Grant Scope + +The licenses granted in this Section 2 are the only rights granted under +this License. No additional rights or licenses will be implied from the +distribution or licensing of Covered Software under this License. +Notwithstanding Section 2.1(b) above, no patent license is granted by a +Contributor: + +(a) for any code that a Contributor has removed from Covered Software; + or + +(b) for infringements caused by: (i) Your and any other third party's + modifications of Covered Software, or (ii) the combination of its + Contributions with other software (except as part of its Contributor + Version); or + +(c) under Patent Claims infringed by Covered Software in the absence of + its Contributions. + +This License does not grant any rights in the trademarks, service marks, +or logos of any Contributor (except as may be necessary to comply with +the notice requirements in Section 3.4). + +2.4. Subsequent Licenses + +No Contributor makes additional grants as a result of Your choice to +distribute the Covered Software under a subsequent version of this +License (see Section 10.2) or under the terms of a Secondary License (if +permitted under the terms of Section 3.3). + +2.5. Representation + +Each Contributor represents that the Contributor believes its +Contributions are its original creation(s) or it has sufficient rights +to grant the rights to its Contributions conveyed by this License. + +2.6. Fair Use + +This License is not intended to limit any rights You have under +applicable copyright doctrines of fair use, fair dealing, or other +equivalents. + +2.7. Conditions + +Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted +in Section 2.1. + +3. Responsibilities +------------------- + +3.1. Distribution of Source Form + +All distribution of Covered Software in Source Code Form, including any +Modifications that You create or to which You contribute, must be under +the terms of this License. You must inform recipients that the Source +Code Form of the Covered Software is governed by the terms of this +License, and how they can obtain a copy of this License. You may not +attempt to alter or restrict the recipients' rights in the Source Code +Form. + +3.2. Distribution of Executable Form + +If You distribute Covered Software in Executable Form then: + +(a) such Covered Software must also be made available in Source Code + Form, as described in Section 3.1, and You must inform recipients of + the Executable Form how they can obtain a copy of such Source Code + Form by reasonable means in a timely manner, at a charge no more + than the cost of distribution to the recipient; and + +(b) You may distribute such Executable Form under the terms of this + License, or sublicense it under different terms, provided that the + license for the Executable Form does not attempt to limit or alter + the recipients' rights in the Source Code Form under this License. + +3.3. Distribution of a Larger Work + +You may create and distribute a Larger Work under terms of Your choice, +provided that You also comply with the requirements of this License for +the Covered Software. If the Larger Work is a combination of Covered +Software with a work governed by one or more Secondary Licenses, and the +Covered Software is not Incompatible With Secondary Licenses, this +License permits You to additionally distribute such Covered Software +under the terms of such Secondary License(s), so that the recipient of +the Larger Work may, at their option, further distribute the Covered +Software under the terms of either this License or such Secondary +License(s). + +3.4. Notices + +You may not remove or alter the substance of any license notices +(including copyright notices, patent notices, disclaimers of warranty, +or limitations of liability) contained within the Source Code Form of +the Covered Software, except that You may alter any license notices to +the extent required to remedy known factual inaccuracies. + +3.5. Application of Additional Terms + +You may choose to offer, and to charge a fee for, warranty, support, +indemnity or liability obligations to one or more recipients of Covered +Software. However, You may do so only on Your own behalf, and not on +behalf of any Contributor. You must make it absolutely clear that any +such warranty, support, indemnity, or liability obligation is offered by +You alone, and You hereby agree to indemnify every Contributor for any +liability incurred by such Contributor as a result of warranty, support, +indemnity or liability terms You offer. You may include additional +disclaimers of warranty and limitations of liability specific to any +jurisdiction. + +4. Inability to Comply Due to Statute or Regulation +--------------------------------------------------- + +If it is impossible for You to comply with any of the terms of this +License with respect to some or all of the Covered Software due to +statute, judicial order, or regulation then You must: (a) comply with +the terms of this License to the maximum extent possible; and (b) +describe the limitations and the code they affect. Such description must +be placed in a text file included with all distributions of the Covered +Software under this License. Except to the extent prohibited by statute +or regulation, such description must be sufficiently detailed for a +recipient of ordinary skill to be able to understand it. + +5. Termination +-------------- + +5.1. The rights granted under this License will terminate automatically +if You fail to comply with any of its terms. However, if You become +compliant, then the rights granted under this License from a particular +Contributor are reinstated (a) provisionally, unless and until such +Contributor explicitly and finally terminates Your grants, and (b) on an +ongoing basis, if such Contributor fails to notify You of the +non-compliance by some reasonable means prior to 60 days after You have +come back into compliance. Moreover, Your grants from a particular +Contributor are reinstated on an ongoing basis if such Contributor +notifies You of the non-compliance by some reasonable means, this is the +first time You have received notice of non-compliance with this License +from such Contributor, and You become compliant prior to 30 days after +Your receipt of the notice. + +5.2. If You initiate litigation against any entity by asserting a patent +infringement claim (excluding declaratory judgment actions, +counter-claims, and cross-claims) alleging that a Contributor Version +directly or indirectly infringes any patent, then the rights granted to +You by any and all Contributors for the Covered Software under Section +2.1 of this License shall terminate. + +5.3. In the event of termination under Sections 5.1 or 5.2 above, all +end user license agreements (excluding distributors and resellers) which +have been validly granted by You or Your distributors under this License +prior to termination shall survive termination. + +************************************************************************ +* * +* 6. Disclaimer of Warranty * +* ------------------------- * +* * +* Covered Software is provided under this License on an "as is" * +* basis, without warranty of any kind, either expressed, implied, or * +* statutory, including, without limitation, warranties that the * +* Covered Software is free of defects, merchantable, fit for a * +* particular purpose or non-infringing. The entire risk as to the * +* quality and performance of the Covered Software is with You. * +* Should any Covered Software prove defective in any respect, You * +* (not any Contributor) assume the cost of any necessary servicing, * +* repair, or correction. This disclaimer of warranty constitutes an * +* essential part of this License. No use of any Covered Software is * +* authorized under this License except under this disclaimer. * +* * +************************************************************************ + +************************************************************************ +* * +* 7. Limitation of Liability * +* -------------------------- * +* * +* Under no circumstances and under no legal theory, whether tort * +* (including negligence), contract, or otherwise, shall any * +* Contributor, or anyone who distributes Covered Software as * +* permitted above, be liable to You for any direct, indirect, * +* special, incidental, or consequential damages of any character * +* including, without limitation, damages for lost profits, loss of * +* goodwill, work stoppage, computer failure or malfunction, or any * +* and all other commercial damages or losses, even if such party * +* shall have been informed of the possibility of such damages. This * +* limitation of liability shall not apply to liability for death or * +* personal injury resulting from such party's negligence to the * +* extent applicable law prohibits such limitation. Some * +* jurisdictions do not allow the exclusion or limitation of * +* incidental or consequential damages, so this exclusion and * +* limitation may not apply to You. * +* * +************************************************************************ + +8. Litigation +------------- + +Any litigation relating to this License may be brought only in the +courts of a jurisdiction where the defendant maintains its principal +place of business and such litigation shall be governed by laws of that +jurisdiction, without reference to its conflict-of-law provisions. +Nothing in this Section shall prevent a party's ability to bring +cross-claims or counter-claims. + +9. Miscellaneous +---------------- + +This License represents the complete agreement concerning the subject +matter hereof. If any provision of this License is held to be +unenforceable, such provision shall be reformed only to the extent +necessary to make it enforceable. Any law or regulation which provides +that the language of a contract shall be construed against the drafter +shall not be used to construe this License against a Contributor. + +10. Versions of the License +--------------------------- + +10.1. New Versions + +Mozilla Foundation is the license steward. Except as provided in Section +10.3, no one other than the license steward has the right to modify or +publish new versions of this License. Each version will be given a +distinguishing version number. + +10.2. Effect of New Versions + +You may distribute the Covered Software under the terms of the version +of the License under which You originally received the Covered Software, +or under the terms of any subsequent version published by the license +steward. + +10.3. Modified Versions + +If you create software not governed by this License, and you want to +create a new license for such software, you may create and use a +modified version of this License if you rename the license and remove +any references to the name of the license steward (except to note that +such modified license differs from this License). + +10.4. Distributing Source Code Form that is Incompatible With Secondary +Licenses + +If You choose to distribute Source Code Form that is Incompatible With +Secondary Licenses under the terms of this version of the License, the +notice described in Exhibit B of this License must be attached. + +Exhibit A - Source Code Form License Notice +------------------------------------------- + + This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at http://mozilla.org/MPL/2.0/. + +If it is not possible or desirable to put the notice in a particular +file, then You may include the notice in a location (such as a LICENSE +file in a relevant directory) where a recipient would be likely to look +for such a notice. + +You may add additional accurate notices of copyright ownership. + +Exhibit B - "Incompatible With Secondary Licenses" Notice +--------------------------------------------------------- + + This Source Code Form is "Incompatible With Secondary Licenses", as + defined by the Mozilla Public License, v. 2.0. diff --git a/vendor/github.com/hashicorp/packer-plugin-qemu/builder/qemu/artifact.go b/vendor/github.com/hashicorp/packer-plugin-qemu/builder/qemu/artifact.go new file mode 100644 index 000000000..fe7674a47 --- /dev/null +++ b/vendor/github.com/hashicorp/packer-plugin-qemu/builder/qemu/artifact.go @@ -0,0 +1,38 @@ +package qemu + +import ( + "fmt" + "os" +) + +// Artifact is the result of running the Qemu builder, namely a set +// of files associated with the resulting machine. +type Artifact struct { + dir string + f []string + state map[string]interface{} +} + +func (*Artifact) BuilderId() string { + return BuilderId +} + +func (a *Artifact) Files() []string { + return a.f +} + +func (*Artifact) Id() string { + return "VM" +} + +func (a *Artifact) String() string { + return fmt.Sprintf("VM files in directory: %s", a.dir) +} + +func (a *Artifact) State(name string) interface{} { + return a.state[name] +} + +func (a *Artifact) Destroy() error { + return os.RemoveAll(a.dir) +} diff --git a/vendor/github.com/hashicorp/packer-plugin-qemu/builder/qemu/builder.go b/vendor/github.com/hashicorp/packer-plugin-qemu/builder/qemu/builder.go new file mode 100644 index 000000000..c9e63a312 --- /dev/null +++ b/vendor/github.com/hashicorp/packer-plugin-qemu/builder/qemu/builder.go @@ -0,0 +1,229 @@ +package qemu + +import ( + "context" + "errors" + "fmt" + "log" + "os" + "os/exec" + "path/filepath" + + "github.com/hashicorp/hcl/v2/hcldec" + "github.com/hashicorp/packer-plugin-sdk/communicator" + "github.com/hashicorp/packer-plugin-sdk/multistep" + "github.com/hashicorp/packer-plugin-sdk/multistep/commonsteps" + packersdk "github.com/hashicorp/packer-plugin-sdk/packer" +) + +const BuilderId = "transcend.qemu" + +type Builder struct { + config Config + runner multistep.Runner +} + +func (b *Builder) ConfigSpec() hcldec.ObjectSpec { return b.config.FlatMapstructure().HCL2Spec() } + +func (b *Builder) Prepare(raws ...interface{}) ([]string, []string, error) { + warnings, errs := b.config.Prepare(raws...) + if errs != nil { + return nil, warnings, errs + } + + return nil, warnings, nil +} + +func (b *Builder) Run(ctx context.Context, ui packersdk.Ui, hook packersdk.Hook) (packersdk.Artifact, error) { + // Create the driver that we'll use to communicate with Qemu + driver, err := b.newDriver(b.config.QemuBinary) + if err != nil { + return nil, fmt.Errorf("Failed creating Qemu driver: %s", err) + } + + steps := []multistep.Step{} + if !b.config.ISOSkipCache { + steps = append(steps, &commonsteps.StepDownload{ + Checksum: b.config.ISOChecksum, + Description: "ISO", + Extension: b.config.TargetExtension, + ResultKey: "iso_path", + TargetPath: b.config.TargetPath, + Url: b.config.ISOUrls, + }) + } else { + steps = append(steps, &stepSetISO{ + ResultKey: "iso_path", + Url: b.config.ISOUrls, + }) + } + + steps = append(steps, new(stepPrepareOutputDir), + &commonsteps.StepCreateFloppy{ + Files: b.config.FloppyConfig.FloppyFiles, + Directories: b.config.FloppyConfig.FloppyDirectories, + Label: b.config.FloppyConfig.FloppyLabel, + }, + &commonsteps.StepCreateCD{ + Files: b.config.CDConfig.CDFiles, + Label: b.config.CDConfig.CDLabel, + }, + &stepCreateDisk{ + AdditionalDiskSize: b.config.AdditionalDiskSize, + DiskImage: b.config.DiskImage, + DiskSize: b.config.DiskSize, + Format: b.config.Format, + OutputDir: b.config.OutputDir, + UseBackingFile: b.config.UseBackingFile, + VMName: b.config.VMName, + QemuImgArgs: b.config.QemuImgArgs, + }, + &stepCopyDisk{ + DiskImage: b.config.DiskImage, + Format: b.config.Format, + OutputDir: b.config.OutputDir, + UseBackingFile: b.config.UseBackingFile, + VMName: b.config.VMName, + }, + &stepResizeDisk{ + DiskCompression: b.config.DiskCompression, + DiskImage: b.config.DiskImage, + Format: b.config.Format, + OutputDir: b.config.OutputDir, + SkipResizeDisk: b.config.SkipResizeDisk, + VMName: b.config.VMName, + DiskSize: b.config.DiskSize, + QemuImgArgs: b.config.QemuImgArgs, + }, + new(stepHTTPIPDiscover), + commonsteps.HTTPServerFromHTTPConfig(&b.config.HTTPConfig), + &stepPortForward{ + CommunicatorType: b.config.CommConfig.Comm.Type, + NetBridge: b.config.NetBridge, + }, + new(stepConfigureVNC), + &stepRun{ + DiskImage: b.config.DiskImage, + }, + &stepConfigureQMP{ + QMPSocketPath: b.config.QMPSocketPath, + }, + &stepTypeBootCommand{}, + &stepWaitGuestAddress{ + CommunicatorType: b.config.CommConfig.Comm.Type, + NetBridge: b.config.NetBridge, + timeout: b.config.CommConfig.Comm.SSHTimeout, + }, + &communicator.StepConnect{ + Config: &b.config.CommConfig.Comm, + Host: commHost(b.config.CommConfig.Comm.Host()), + SSHConfig: b.config.CommConfig.Comm.SSHConfigFunc(), + SSHPort: commPort, + WinRMPort: commPort, + }, + new(commonsteps.StepProvision), + &commonsteps.StepCleanupTempKeys{ + Comm: &b.config.CommConfig.Comm, + }, + &stepShutdown{ + ShutdownTimeout: b.config.ShutdownTimeout, + ShutdownCommand: b.config.ShutdownCommand, + Comm: &b.config.CommConfig.Comm, + }, + &stepConvertDisk{ + DiskCompression: b.config.DiskCompression, + Format: b.config.Format, + OutputDir: b.config.OutputDir, + SkipCompaction: b.config.SkipCompaction, + VMName: b.config.VMName, + QemuImgArgs: b.config.QemuImgArgs, + }, + ) + + // Setup the state bag + state := new(multistep.BasicStateBag) + state.Put("config", &b.config) + state.Put("debug", b.config.PackerDebug) + state.Put("driver", driver) + state.Put("hook", hook) + state.Put("ui", ui) + + // Run + b.runner = commonsteps.NewRunnerWithPauseFn(steps, b.config.PackerConfig, ui, state) + b.runner.Run(ctx, state) + + // If there was an error, return that + if rawErr, ok := state.GetOk("error"); ok { + return nil, rawErr.(error) + } + + // If we were interrupted or cancelled, then just exit. + if _, ok := state.GetOk(multistep.StateCancelled); ok { + return nil, errors.New("Build was cancelled.") + } + + if _, ok := state.GetOk(multistep.StateHalted); ok { + return nil, errors.New("Build was halted.") + } + + // Compile the artifact list + files := make([]string, 0, 5) + visit := func(path string, info os.FileInfo, err error) error { + if err != nil { + return err + } + if !info.IsDir() { + files = append(files, path) + } + + return nil + } + + if err := filepath.Walk(b.config.OutputDir, visit); err != nil { + return nil, err + } + + artifact := &Artifact{ + dir: b.config.OutputDir, + f: files, + state: make(map[string]interface{}), + } + + artifact.state["generated_data"] = state.Get("generated_data") + artifact.state["diskName"] = b.config.VMName + + // placed in state in step_create_disk.go + diskpaths, ok := state.Get("qemu_disk_paths").([]string) + if ok { + artifact.state["diskPaths"] = diskpaths + } + artifact.state["diskType"] = b.config.Format + artifact.state["diskSize"] = b.config.DiskSize + artifact.state["domainType"] = b.config.Accelerator + + return artifact, nil +} + +func (b *Builder) newDriver(qemuBinary string) (Driver, error) { + qemuPath, err := exec.LookPath(qemuBinary) + if err != nil { + return nil, err + } + + qemuImgPath, err := exec.LookPath("qemu-img") + if err != nil { + return nil, err + } + + log.Printf("Qemu path: %s, Qemu Image page: %s", qemuPath, qemuImgPath) + driver := &QemuDriver{ + QemuPath: qemuPath, + QemuImgPath: qemuImgPath, + } + + if err := driver.Verify(); err != nil { + return nil, err + } + + return driver, nil +} diff --git a/vendor/github.com/hashicorp/packer-plugin-qemu/builder/qemu/comm_config.go b/vendor/github.com/hashicorp/packer-plugin-qemu/builder/qemu/comm_config.go new file mode 100644 index 000000000..99b945507 --- /dev/null +++ b/vendor/github.com/hashicorp/packer-plugin-qemu/builder/qemu/comm_config.go @@ -0,0 +1,73 @@ +//go:generate packer-sdc struct-markdown +package qemu + +import ( + "errors" + + "github.com/hashicorp/packer-plugin-sdk/communicator" + "github.com/hashicorp/packer-plugin-sdk/template/interpolate" +) + +type CommConfig struct { + Comm communicator.Config `mapstructure:",squash"` + // The minimum port to use for the Communicator port on the host machine which is forwarded + // to the SSH or WinRM port on the guest machine. By default this is 2222. + HostPortMin int `mapstructure:"host_port_min" required:"false"` + // The maximum port to use for the Communicator port on the host machine which is forwarded + // to the SSH or WinRM port on the guest machine. Because Packer often runs in parallel, + // Packer will choose a randomly available port in this range to use as the + // host port. By default this is 4444. + HostPortMax int `mapstructure:"host_port_max" required:"false"` + // Defaults to false. When enabled, Packer + // does not setup forwarded port mapping for communicator (SSH or WinRM) requests and uses ssh_port or winrm_port + // on the host to communicate to the virtual machine. + SkipNatMapping bool `mapstructure:"skip_nat_mapping" required:"false"` + + // These are deprecated, but we keep them around for backwards compatibility + // TODO: remove later + SSHHostPortMin int `mapstructure:"ssh_host_port_min" required:"false"` + // TODO: remove later + SSHHostPortMax int `mapstructure:"ssh_host_port_max"` +} + +func (c *CommConfig) Prepare(ctx *interpolate.Context) (warnings []string, errs []error) { + + // Backwards compatibility + if c.SSHHostPortMin != 0 { + warnings = append(warnings, "ssh_host_port_min is deprecated and is being replaced by host_port_min. "+ + "Please, update your template to use host_port_min. In future versions of Packer, inclusion of ssh_host_port_min will error your builds.") + c.HostPortMin = c.SSHHostPortMin + } + + // Backwards compatibility + if c.SSHHostPortMax != 0 { + warnings = append(warnings, "ssh_host_port_max is deprecated and is being replaced by host_port_max. "+ + "Please, update your template to use host_port_max. In future versions of Packer, inclusion of ssh_host_port_max will error your builds.") + c.HostPortMax = c.SSHHostPortMax + } + + if c.Comm.SSHHost == "" && c.SkipNatMapping { + c.Comm.SSHHost = "127.0.0.1" + c.Comm.WinRMHost = "127.0.0.1" + } + + if c.HostPortMin == 0 { + c.HostPortMin = 2222 + } + + if c.HostPortMax == 0 { + c.HostPortMax = 4444 + } + + errs = c.Comm.Prepare(ctx) + if c.HostPortMin > c.HostPortMax { + errs = append(errs, + errors.New("host_port_min must be less than host_port_max")) + } + + if c.HostPortMin < 0 { + errs = append(errs, errors.New("host_port_min must be positive")) + } + + return +} diff --git a/vendor/github.com/hashicorp/packer-plugin-qemu/builder/qemu/config.go b/vendor/github.com/hashicorp/packer-plugin-qemu/builder/qemu/config.go new file mode 100644 index 000000000..4a8cf487e --- /dev/null +++ b/vendor/github.com/hashicorp/packer-plugin-qemu/builder/qemu/config.go @@ -0,0 +1,642 @@ +//go:generate packer-sdc struct-markdown +//go:generate packer-sdc mapstructure-to-hcl2 -type Config,QemuImgArgs + +package qemu + +import ( + "errors" + "fmt" + "log" + "os" + "path/filepath" + "regexp" + "runtime" + "strings" + + "github.com/hashicorp/packer-plugin-sdk/bootcommand" + "github.com/hashicorp/packer-plugin-sdk/common" + "github.com/hashicorp/packer-plugin-sdk/multistep/commonsteps" + packersdk "github.com/hashicorp/packer-plugin-sdk/packer" + "github.com/hashicorp/packer-plugin-sdk/shutdowncommand" + "github.com/hashicorp/packer-plugin-sdk/template/config" + "github.com/hashicorp/packer-plugin-sdk/template/interpolate" +) + +var accels = map[string]struct{}{ + "none": {}, + "kvm": {}, + "tcg": {}, + "xen": {}, + "hax": {}, + "hvf": {}, + "whpx": {}, +} + +var diskInterface = map[string]bool{ + "ide": true, + "scsi": true, + "virtio": true, + "virtio-scsi": true, +} + +var diskCache = map[string]bool{ + "writethrough": true, + "writeback": true, + "none": true, + "unsafe": true, + "directsync": true, +} + +var diskDiscard = map[string]bool{ + "unmap": true, + "ignore": true, +} + +var diskDZeroes = map[string]bool{ + "unmap": true, + "on": true, + "off": true, +} + +type QemuImgArgs struct { + Convert []string `mapstructure:"convert" required:"false"` + Create []string `mapstructure:"create" required:"false"` + Resize []string `mapstructure:"resize" required:"false"` +} + +type Config struct { + common.PackerConfig `mapstructure:",squash"` + commonsteps.HTTPConfig `mapstructure:",squash"` + commonsteps.ISOConfig `mapstructure:",squash"` + bootcommand.VNCConfig `mapstructure:",squash"` + shutdowncommand.ShutdownConfig `mapstructure:",squash"` + CommConfig CommConfig `mapstructure:",squash"` + commonsteps.FloppyConfig `mapstructure:",squash"` + commonsteps.CDConfig `mapstructure:",squash"` + // Use iso from provided url. Qemu must support + // curl block device. This defaults to `false`. + ISOSkipCache bool `mapstructure:"iso_skip_cache" required:"false"` + // The accelerator type to use when running the VM. + // This may be `none`, `kvm`, `tcg`, `hax`, `hvf`, `whpx`, or `xen`. The appropriate + // software must have already been installed on your build machine to use the + // accelerator you specified. When no accelerator is specified, Packer will try + // to use `kvm` if it is available but will default to `tcg` otherwise. + // + // ~> The `hax` accelerator has issues attaching CDROM ISOs. This is an + // upstream issue which can be tracked + // [here](https://github.com/intel/haxm/issues/20). + // + // ~> The `hvf` and `whpx` accelerator are new and experimental as of + // [QEMU 2.12.0](https://wiki.qemu.org/ChangeLog/2.12#Host_support). + // You may encounter issues unrelated to Packer when using these. You may need to + // add [ "-global", "virtio-pci.disable-modern=on" ] to `qemuargs` depending on the + // guest operating system. + // + // ~> For `whpx`, note that [Stefan Weil's QEMU for Windows distribution](https://qemu.weilnetz.de/w64/) + // does not include WHPX support and users may need to compile or source a + // build of QEMU for Windows themselves with WHPX support. + Accelerator string `mapstructure:"accelerator" required:"false"` + // Additional disks to create. Uses `vm_name` as the disk name template and + // appends `-#` where `#` is the position in the array. `#` starts at 1 since 0 + // is the default disk. Each string represents the disk image size in bytes. + // Optional suffixes 'k' or 'K' (kilobyte, 1024), 'M' (megabyte, 1024k), 'G' + // (gigabyte, 1024M), 'T' (terabyte, 1024G), 'P' (petabyte, 1024T) and 'E' + // (exabyte, 1024P) are supported. 'b' is ignored. Per qemu-img documentation. + // Each additional disk uses the same disk parameters as the default disk. + // Unset by default. + AdditionalDiskSize []string `mapstructure:"disk_additional_size" required:"false"` + // The number of cpus to use when building the VM. + // The default is `1` CPU. + CpuCount int `mapstructure:"cpus" required:"false"` + // The firmware file to be used by QEMU, which is to be set by the -bios + // option of QEMU. Particularly, this option can be set to use EFI instead + // of BIOS, by using "OVMF.fd" from OpenFirmware. + // If unset, no -bios option is passed to QEMU, using the default of QEMU. + // Also see the QEMU documentation. + Firmware string `mapstructure:"firmware" required:"false"` + // The interface to use for the disk. Allowed values include any of `ide`, + // `scsi`, `virtio` or `virtio-scsi`^\*. Note also that any boot commands + // or kickstart type scripts must have proper adjustments for resulting + // device names. The Qemu builder uses `virtio` by default. + // + // ^\* Please be aware that use of the `scsi` disk interface has been + // disabled by Red Hat due to a bug described + // [here](https://bugzilla.redhat.com/show_bug.cgi?id=1019220). If you are + // running Qemu on RHEL or a RHEL variant such as CentOS, you *must* choose + // one of the other listed interfaces. Using the `scsi` interface under + // these circumstances will cause the build to fail. + DiskInterface string `mapstructure:"disk_interface" required:"false"` + // The size in bytes of the hard disk of the VM. Suffix with the first + // letter of common byte types. Use "k" or "K" for kilobytes, "M" for + // megabytes, G for gigabytes, and T for terabytes. If no value is provided + // for disk_size, Packer uses a default of `40960M` (40 GB). If a disk_size + // number is provided with no units, Packer will default to Megabytes. + DiskSize string `mapstructure:"disk_size" required:"false"` + // Packer resizes the QCOW2 image using + // qemu-img resize. Set this option to true to disable resizing. + // Defaults to false. + SkipResizeDisk bool `mapstructure:"skip_resize_disk" required:"false"` + // The cache mode to use for disk. Allowed values include any of + // `writethrough`, `writeback`, `none`, `unsafe` or `directsync`. By + // default, this is set to `writeback`. + DiskCache string `mapstructure:"disk_cache" required:"false"` + // The discard mode to use for disk. Allowed values + // include any of unmap or ignore. By default, this is set to ignore. + DiskDiscard string `mapstructure:"disk_discard" required:"false"` + // The detect-zeroes mode to use for disk. + // Allowed values include any of unmap, on or off. Defaults to off. + // When the value is "off" we don't set the flag in the qemu command, so that + // Packer still works with old versions of QEMU that don't have this option. + DetectZeroes string `mapstructure:"disk_detect_zeroes" required:"false"` + // Packer compacts the QCOW2 image using + // qemu-img convert. Set this option to true to disable compacting. + // Defaults to false. + SkipCompaction bool `mapstructure:"skip_compaction" required:"false"` + // Apply compression to the QCOW2 disk file + // using qemu-img convert. Defaults to false. + DiskCompression bool `mapstructure:"disk_compression" required:"false"` + // Either `qcow2` or `raw`, this specifies the output format of the virtual + // machine image. This defaults to `qcow2`. Due to a long-standing bug with + // `qemu-img convert` on OSX, sometimes the qemu-img convert call will + // create a corrupted image. If this is an issue for you, make sure that the + // the output format matches the input file's format, and Packer will + // perform a simple copy operation instead. See + // https://bugs.launchpad.net/qemu/+bug/1776920 for more details. + Format string `mapstructure:"format" required:"false"` + // Packer defaults to building QEMU virtual machines by + // launching a GUI that shows the console of the machine being built. When this + // value is set to `true`, the machine will start without a console. + // + // You can still see the console if you make a note of the VNC display + // number chosen, and then connect using `vncviewer -Shared :` + Headless bool `mapstructure:"headless" required:"false"` + // Packer defaults to building from an ISO file, this parameter controls + // whether the ISO URL supplied is actually a bootable QEMU image. When + // this value is set to `true`, the machine will either clone the source or + // use it as a backing file (if `use_backing_file` is `true`); then, it + // will resize the image according to `disk_size` and boot it. + DiskImage bool `mapstructure:"disk_image" required:"false"` + // Only applicable when disk_image is true + // and format is qcow2, set this option to true to create a new QCOW2 + // file that uses the file located at iso_url as a backing file. The new file + // will only contain blocks that have changed compared to the backing file, so + // enabling this option can significantly reduce disk usage. If true, Packer + // will force the `skip_compaction` also to be true as well to skip disk + // conversion which would render the backing file feature useless. + UseBackingFile bool `mapstructure:"use_backing_file" required:"false"` + // The type of machine emulation to use. Run your qemu binary with the + // flags `-machine help` to list available types for your system. This + // defaults to `pc`. + MachineType string `mapstructure:"machine_type" required:"false"` + // The amount of memory to use when building the VM + // in megabytes. This defaults to 512 megabytes. + MemorySize int `mapstructure:"memory" required:"false"` + // The driver to use for the network interface. Allowed values `ne2k_pci`, + // `i82551`, `i82557b`, `i82559er`, `rtl8139`, `e1000`, `pcnet`, `virtio`, + // `virtio-net`, `virtio-net-pci`, `usb-net`, `i82559a`, `i82559b`, + // `i82559c`, `i82550`, `i82562`, `i82557a`, `i82557c`, `i82801`, + // `vmxnet3`, `i82558a` or `i82558b`. The Qemu builder uses `virtio-net` by + // default. + NetDevice string `mapstructure:"net_device" required:"false"` + // Connects the network to this bridge instead of using the user mode + // networking. + // + // **NB** This bridge must already exist. You can use the `virbr0` bridge + // as created by vagrant-libvirt. + // + // **NB** This will automatically enable the QMP socket (see QMPEnable). + // + // **NB** This only works in Linux based OSes. + NetBridge string `mapstructure:"net_bridge" required:"false"` + // This is the path to the directory where the + // resulting virtual machine will be created. This may be relative or absolute. + // If relative, the path is relative to the working directory when packer + // is executed. This directory must not exist or be empty prior to running + // the builder. By default this is output-BUILDNAME where "BUILDNAME" is the + // name of the build. + OutputDir string `mapstructure:"output_directory" required:"false"` + // Allows complete control over the qemu command line (though not qemu-img). + // Each array of strings makes up a command line switch + // that overrides matching default switch/value pairs. Any value specified + // as an empty string is ignored. All values after the switch are + // concatenated with no separator. + // + // ~> **Warning:** The qemu command line allows extreme flexibility, so + // beware of conflicting arguments causing failures of your run. + // For instance adding a "--drive" or "--device" override will mean that + // none of the default configuration Packer sets will be used. To see the + // defaults that Packer sets, look in your packer.log + // file (set PACKER_LOG=1 to get verbose logging) and search for the + // qemu-system-x86 command. The arguments are all printed for review, and + // you can use those arguments along with the template engines allowed + // by qemu-args to set up a working configuration that includes both the + // Packer defaults and your extra arguments. + // + // Another pitfall could be setting arguments like --no-acpi, which could + // break the ability to send power signal type commands + // (e.g., shutdown -P now) to the virtual machine, thus preventing proper + // shutdown. + // + // The following shows a sample usage: + // + // In JSON: + // ```json + // "qemuargs": [ + // [ "-m", "1024M" ], + // [ "--no-acpi", "" ], + // [ + // "-netdev", + // "user,id=mynet0,", + // "hostfwd=hostip:hostport-guestip:guestport", + // "" + // ], + // [ "-device", "virtio-net,netdev=mynet0" ] + // ] + // ``` + // + // In HCL2: + // ```hcl + // qemuargs = [ + // [ "-m", "1024M" ], + // [ "--no-acpi", "" ], + // [ + // "-netdev", + // "user,id=mynet0,", + // "hostfwd=hostip:hostport-guestip:guestport", + // "" + // ], + // [ "-device", "virtio-net,netdev=mynet0" ] + // ] + // ``` + // + // would produce the following (not including other defaults supplied by + // the builder and not otherwise conflicting with the qemuargs): + // + // ```text + // qemu-system-x86 -m 1024m --no-acpi -netdev + // user,id=mynet0,hostfwd=hostip:hostport-guestip:guestport -device + // virtio-net,netdev=mynet0" + // ``` + // + // ~> **Windows Users:** [QEMU for Windows](https://qemu.weilnetz.de/) + // builds are available though an environmental variable does need to be + // set for QEMU for Windows to redirect stdout to the console instead of + // stdout.txt. + // + // The following shows the environment variable that needs to be set for + // Windows QEMU support: + // + // ```text + // setx SDL_STDIO_REDIRECT=0 + // ``` + // + // You can also use the `SSHHostPort` template variable to produce a packer + // template that can be invoked by `make` in parallel: + // + // In JSON: + // ```json + // "qemuargs": [ + // [ "-netdev", "user,hostfwd=tcp::{{ .SSHHostPort }}-:22,id=forward"], + // [ "-device", "virtio-net,netdev=forward,id=net0"] + // ] + // ``` + // + // In HCL2: + // ```hcl + // qemuargs = [ + // [ "-netdev", "user,hostfwd=tcp::{{ .SSHHostPort }}-:22,id=forward"], + // [ "-device", "virtio-net,netdev=forward,id=net0"] + // ] + // + // `make -j 3 my-awesome-packer-templates` spawns 3 packer processes, each + // of which will bind to their own SSH port as determined by each process. + // This will also work with WinRM, just change the port forward in + // `qemuargs` to map to WinRM's default port of `5985` or whatever value + // you have the service set to listen on. + // + // This is a template engine and allows access to the following variables: + // `{{ .HTTPIP }}`, `{{ .HTTPPort }}`, `{{ .HTTPDir }}`, + // `{{ .OutputDir }}`, `{{ .Name }}`, and `{{ .SSHHostPort }}` + QemuArgs [][]string `mapstructure:"qemuargs" required:"false"` + // A map of custom arguments to pass to qemu-img commands, where the key + // is the subcommand, and the values are lists of strings for each flag. + // Example: + // + // In JSON: + // ```json + // { + // "qemu_img_args": { + // "convert": ["-o", "preallocation=full"], + // "resize": ["-foo", "bar"] + // } + // ``` + // Please note + // that unlike qemuargs, these commands are not split into switch-value + // sub-arrays, because the basic elements in qemu-img calls are unlikely + // to need an actual override. + // The arguments will be constructed as follows: + // - Convert: + // Default is `qemu-img convert -O $format $sourcepath $targetpath`. Adding + // arguments ["-foo", "bar"] to qemu_img_args.convert will change this to + // `qemu-img convert -foo bar -O $format $sourcepath $targetpath` + // - Create: + // Default is `create -f $format $targetpath $size`. Adding arguments + // ["-foo", "bar"] to qemu_img_args.create will change this to + // "create -f qcow2 -foo bar target.qcow2 1234M" + // - Resize: + // Default is `qemu-img resize -f $format $sourcepath $size`. Adding + // arguments ["-foo", "bar"] to qemu_img_args.resize will change this to + // `qemu-img resize -f $format -foo bar $sourcepath $size` + QemuImgArgs QemuImgArgs `mapstructure:"qemu_img_args" required:"false"` + // The name of the Qemu binary to look for. This + // defaults to qemu-system-x86_64, but may need to be changed for + // some platforms. For example qemu-kvm, or qemu-system-i386 may be a + // better choice for some systems. + QemuBinary string `mapstructure:"qemu_binary" required:"false"` + // Enable QMP socket. Location is specified by `qmp_socket_path`. Defaults + // to false. + QMPEnable bool `mapstructure:"qmp_enable" required:"false"` + // QMP Socket Path when `qmp_enable` is true. Defaults to + // `output_directory`/`vm_name`.monitor. + QMPSocketPath string `mapstructure:"qmp_socket_path" required:"false"` + // If true, do not pass a -display option + // to qemu, allowing it to choose the default. This may be needed when running + // under macOS, and getting errors about sdl not being available. + UseDefaultDisplay bool `mapstructure:"use_default_display" required:"false"` + // What QEMU -display option to use. Defaults to gtk, use none to not pass the + // -display option allowing QEMU to choose the default. This may be needed when + // running under macOS, and getting errors about sdl not being available. + Display string `mapstructure:"display" required:"false"` + // The IP address that should be + // binded to for VNC. By default packer will use 127.0.0.1 for this. If you + // wish to bind to all interfaces use 0.0.0.0. + VNCBindAddress string `mapstructure:"vnc_bind_address" required:"false"` + // Whether or not to set a password on the VNC server. This option + // automatically enables the QMP socket. See `qmp_socket_path`. Defaults to + // `false`. + VNCUsePassword bool `mapstructure:"vnc_use_password" required:"false"` + // The minimum and maximum port + // to use for VNC access to the virtual machine. The builder uses VNC to type + // the initial boot_command. Because Packer generally runs in parallel, + // Packer uses a randomly chosen port in this range that appears available. By + // default this is 5900 to 6000. The minimum and maximum ports are inclusive. + // The minimum port cannot be set below 5900 due to a quirk in how QEMU parses + // vnc display address. + VNCPortMin int `mapstructure:"vnc_port_min" required:"false"` + VNCPortMax int `mapstructure:"vnc_port_max"` + // This is the name of the image (QCOW2 or IMG) file for + // the new virtual machine. By default this is packer-BUILDNAME, where + // "BUILDNAME" is the name of the build. Currently, no file extension will be + // used unless it is specified in this option. + VMName string `mapstructure:"vm_name" required:"false"` + // The interface to use for the CDROM device which contains the ISO image. + // Allowed values include any of `ide`, `scsi`, `virtio` or + // `virtio-scsi`. The Qemu builder uses `virtio` by default. + // Some ARM64 images require `virtio-scsi`. + CDROMInterface string `mapstructure:"cdrom_interface" required:"false"` + + // TODO(mitchellh): deprecate + RunOnce bool `mapstructure:"run_once"` + + ctx interpolate.Context +} + +func (c *Config) Prepare(raws ...interface{}) ([]string, error) { + err := config.Decode(c, &config.DecodeOpts{ + PluginType: BuilderId, + Interpolate: true, + InterpolateContext: &c.ctx, + InterpolateFilter: &interpolate.RenderFilter{ + Exclude: []string{ + "boot_command", + "qemuargs", + }, + }, + }, raws...) + if err != nil { + return nil, err + } + + // Accumulate any errors and warnings + var errs *packersdk.MultiError + warnings := make([]string, 0) + + errs = packersdk.MultiErrorAppend(errs, c.ShutdownConfig.Prepare(&c.ctx)...) + + if c.DiskSize == "" || c.DiskSize == "0" { + c.DiskSize = "40960M" + } else { + // Make sure supplied disk size is valid + // (digits, plus an optional valid unit character). e.g. 5000, 40G, 1t + re := regexp.MustCompile(`^[\d]+(b|k|m|g|t){0,1}$`) + matched := re.MatchString(strings.ToLower(c.DiskSize)) + if !matched { + errs = packersdk.MultiErrorAppend(errs, fmt.Errorf("Invalid disk size.")) + } else { + // Okay, it's valid -- if it doesn't alreay have a suffix, then + // append "M" as the default unit. + re = regexp.MustCompile(`^[\d]+$`) + matched = re.MatchString(strings.ToLower(c.DiskSize)) + if matched { + // Needs M added. + c.DiskSize = fmt.Sprintf("%sM", c.DiskSize) + } + } + } + + if c.DiskCache == "" { + c.DiskCache = "writeback" + } + + if c.DiskDiscard == "" { + c.DiskDiscard = "ignore" + } + + if c.DetectZeroes == "" { + c.DetectZeroes = "off" + } + + if c.Accelerator == "" { + if runtime.GOOS == "windows" { + c.Accelerator = "tcg" + } else { + // /dev/kvm is a kernel module that may be loaded if kvm is + // installed and the host supports VT-x extensions. To make sure + // this will actually work we need to os.Open() it. If os.Open fails + // the kernel module was not installed or loaded correctly. + if fp, err := os.Open("/dev/kvm"); err != nil { + c.Accelerator = "tcg" + } else { + fp.Close() + c.Accelerator = "kvm" + } + } + log.Printf("use detected accelerator: %s", c.Accelerator) + } else { + log.Printf("use specified accelerator: %s", c.Accelerator) + } + + if c.MachineType == "" { + c.MachineType = "pc" + } + + if c.OutputDir == "" { + c.OutputDir = fmt.Sprintf("output-%s", c.PackerBuildName) + } + + if c.QemuBinary == "" { + c.QemuBinary = "qemu-system-x86_64" + } + + if c.MemorySize < 10 { + log.Printf("MemorySize %d is too small, using default: 512", c.MemorySize) + c.MemorySize = 512 + } + + if c.CpuCount < 1 { + log.Printf("CpuCount %d too small, using default: 1", c.CpuCount) + c.CpuCount = 1 + } + + if c.VNCBindAddress == "" { + c.VNCBindAddress = "127.0.0.1" + } + + if c.VNCPortMin == 0 { + c.VNCPortMin = 5900 + } + + if c.VNCPortMax == 0 { + c.VNCPortMax = 6000 + } + + if c.VMName == "" { + c.VMName = fmt.Sprintf("packer-%s", c.PackerBuildName) + } + + if c.Format == "" { + c.Format = "qcow2" + } + + errs = packersdk.MultiErrorAppend(errs, c.FloppyConfig.Prepare(&c.ctx)...) + errs = packersdk.MultiErrorAppend(errs, c.CDConfig.Prepare(&c.ctx)...) + errs = packersdk.MultiErrorAppend(errs, c.VNCConfig.Prepare(&c.ctx)...) + + if c.NetDevice == "" { + c.NetDevice = "virtio-net" + } + + if c.DiskInterface == "" { + c.DiskInterface = "virtio" + } + + if c.ISOSkipCache { + c.ISOChecksum = "none" + } + isoWarnings, isoErrs := c.ISOConfig.Prepare(&c.ctx) + warnings = append(warnings, isoWarnings...) + errs = packersdk.MultiErrorAppend(errs, isoErrs...) + + errs = packersdk.MultiErrorAppend(errs, c.HTTPConfig.Prepare(&c.ctx)...) + commConfigWarnings, es := c.CommConfig.Prepare(&c.ctx) + if len(es) > 0 { + errs = packersdk.MultiErrorAppend(errs, es...) + } + warnings = append(warnings, commConfigWarnings...) + + if !(c.Format == "qcow2" || c.Format == "raw") { + errs = packersdk.MultiErrorAppend( + errs, errors.New("invalid format, only 'qcow2' or 'raw' are allowed")) + } + + if c.Format != "qcow2" { + c.SkipCompaction = true + c.DiskCompression = false + } + + if c.UseBackingFile { + c.SkipCompaction = true + if !(c.DiskImage && c.Format == "qcow2") { + errs = packersdk.MultiErrorAppend( + errs, errors.New("use_backing_file can only be enabled for QCOW2 images and when disk_image is true")) + } + } + + if c.SkipResizeDisk && !(c.DiskImage) { + errs = packersdk.MultiErrorAppend( + errs, errors.New("skip_resize_disk can only be used when disk_image is true")) + } + + if _, ok := accels[c.Accelerator]; !ok { + errs = packersdk.MultiErrorAppend( + errs, errors.New("invalid accelerator, only 'kvm', 'tcg', 'xen', 'hax', 'hvf', 'whpx', or 'none' are allowed")) + } + + if _, ok := diskInterface[c.DiskInterface]; !ok { + errs = packersdk.MultiErrorAppend( + errs, errors.New("unrecognized disk interface type")) + } + + if _, ok := diskCache[c.DiskCache]; !ok { + errs = packersdk.MultiErrorAppend( + errs, errors.New("unrecognized disk cache type")) + } + + if _, ok := diskDiscard[c.DiskDiscard]; !ok { + errs = packersdk.MultiErrorAppend( + errs, errors.New("unrecognized disk discard type")) + } + + if _, ok := diskDZeroes[c.DetectZeroes]; !ok { + errs = packersdk.MultiErrorAppend( + errs, errors.New("unrecognized disk detect zeroes setting")) + } + + if !c.PackerForce { + if _, err := os.Stat(c.OutputDir); err == nil { + errs = packersdk.MultiErrorAppend( + errs, + fmt.Errorf("Output directory '%s' already exists. It must not exist.", c.OutputDir)) + } + } + + if c.VNCPortMin < 5900 { + errs = packersdk.MultiErrorAppend( + errs, fmt.Errorf("vnc_port_min cannot be below 5900")) + } + + if c.VNCPortMin > 65535 || c.VNCPortMax > 65535 { + errs = packersdk.MultiErrorAppend( + errs, fmt.Errorf("vmc_port_min and vnc_port_max must both be below 65535 to be valid TCP ports")) + } + + if c.VNCPortMin > c.VNCPortMax { + errs = packersdk.MultiErrorAppend( + errs, fmt.Errorf("vnc_port_min must be less than vnc_port_max")) + } + + if c.NetBridge != "" && runtime.GOOS != "linux" { + errs = packersdk.MultiErrorAppend( + errs, fmt.Errorf("net_bridge is only supported in Linux based OSes")) + } + + if c.NetBridge != "" || c.VNCUsePassword { + c.QMPEnable = true + } + + if c.QMPEnable && c.QMPSocketPath == "" { + socketName := fmt.Sprintf("%s.monitor", c.VMName) + c.QMPSocketPath = filepath.Join(c.OutputDir, socketName) + } + + if c.QemuArgs == nil { + c.QemuArgs = make([][]string, 0) + } + + if errs != nil && len(errs.Errors) > 0 { + return warnings, errs + } + + return warnings, nil + +} diff --git a/vendor/github.com/hashicorp/packer-plugin-qemu/builder/qemu/config.hcl2spec.go b/vendor/github.com/hashicorp/packer-plugin-qemu/builder/qemu/config.hcl2spec.go new file mode 100644 index 000000000..899b95885 --- /dev/null +++ b/vendor/github.com/hashicorp/packer-plugin-qemu/builder/qemu/config.hcl2spec.go @@ -0,0 +1,298 @@ +// Code generated by "packer-sdc mapstructure-to-hcl2"; DO NOT EDIT. + +package qemu + +import ( + "github.com/hashicorp/hcl/v2/hcldec" + "github.com/zclconf/go-cty/cty" +) + +// FlatConfig is an auto-generated flat version of Config. +// Where the contents of a field with a `mapstructure:,squash` tag are bubbled up. +type FlatConfig struct { + PackerBuildName *string `mapstructure:"packer_build_name" cty:"packer_build_name" hcl:"packer_build_name"` + PackerBuilderType *string `mapstructure:"packer_builder_type" cty:"packer_builder_type" hcl:"packer_builder_type"` + PackerCoreVersion *string `mapstructure:"packer_core_version" cty:"packer_core_version" hcl:"packer_core_version"` + PackerDebug *bool `mapstructure:"packer_debug" cty:"packer_debug" hcl:"packer_debug"` + PackerForce *bool `mapstructure:"packer_force" cty:"packer_force" hcl:"packer_force"` + PackerOnError *string `mapstructure:"packer_on_error" cty:"packer_on_error" hcl:"packer_on_error"` + PackerUserVars map[string]string `mapstructure:"packer_user_variables" cty:"packer_user_variables" hcl:"packer_user_variables"` + PackerSensitiveVars []string `mapstructure:"packer_sensitive_variables" cty:"packer_sensitive_variables" hcl:"packer_sensitive_variables"` + HTTPDir *string `mapstructure:"http_directory" cty:"http_directory" hcl:"http_directory"` + HTTPContent map[string]string `mapstructure:"http_content" cty:"http_content" hcl:"http_content"` + HTTPPortMin *int `mapstructure:"http_port_min" cty:"http_port_min" hcl:"http_port_min"` + HTTPPortMax *int `mapstructure:"http_port_max" cty:"http_port_max" hcl:"http_port_max"` + HTTPAddress *string `mapstructure:"http_bind_address" cty:"http_bind_address" hcl:"http_bind_address"` + HTTPInterface *string `mapstructure:"http_interface" undocumented:"true" cty:"http_interface" hcl:"http_interface"` + ISOChecksum *string `mapstructure:"iso_checksum" required:"true" cty:"iso_checksum" hcl:"iso_checksum"` + RawSingleISOUrl *string `mapstructure:"iso_url" required:"true" cty:"iso_url" hcl:"iso_url"` + ISOUrls []string `mapstructure:"iso_urls" cty:"iso_urls" hcl:"iso_urls"` + TargetPath *string `mapstructure:"iso_target_path" cty:"iso_target_path" hcl:"iso_target_path"` + TargetExtension *string `mapstructure:"iso_target_extension" cty:"iso_target_extension" hcl:"iso_target_extension"` + BootGroupInterval *string `mapstructure:"boot_keygroup_interval" cty:"boot_keygroup_interval" hcl:"boot_keygroup_interval"` + BootWait *string `mapstructure:"boot_wait" cty:"boot_wait" hcl:"boot_wait"` + BootCommand []string `mapstructure:"boot_command" cty:"boot_command" hcl:"boot_command"` + DisableVNC *bool `mapstructure:"disable_vnc" cty:"disable_vnc" hcl:"disable_vnc"` + BootKeyInterval *string `mapstructure:"boot_key_interval" cty:"boot_key_interval" hcl:"boot_key_interval"` + ShutdownCommand *string `mapstructure:"shutdown_command" required:"false" cty:"shutdown_command" hcl:"shutdown_command"` + ShutdownTimeout *string `mapstructure:"shutdown_timeout" required:"false" cty:"shutdown_timeout" hcl:"shutdown_timeout"` + Type *string `mapstructure:"communicator" cty:"communicator" hcl:"communicator"` + PauseBeforeConnect *string `mapstructure:"pause_before_connecting" cty:"pause_before_connecting" hcl:"pause_before_connecting"` + SSHHost *string `mapstructure:"ssh_host" cty:"ssh_host" hcl:"ssh_host"` + SSHPort *int `mapstructure:"ssh_port" cty:"ssh_port" hcl:"ssh_port"` + SSHUsername *string `mapstructure:"ssh_username" cty:"ssh_username" hcl:"ssh_username"` + SSHPassword *string `mapstructure:"ssh_password" cty:"ssh_password" hcl:"ssh_password"` + SSHKeyPairName *string `mapstructure:"ssh_keypair_name" undocumented:"true" cty:"ssh_keypair_name" hcl:"ssh_keypair_name"` + SSHTemporaryKeyPairName *string `mapstructure:"temporary_key_pair_name" undocumented:"true" cty:"temporary_key_pair_name" hcl:"temporary_key_pair_name"` + SSHTemporaryKeyPairType *string `mapstructure:"temporary_key_pair_type" cty:"temporary_key_pair_type" hcl:"temporary_key_pair_type"` + SSHTemporaryKeyPairBits *int `mapstructure:"temporary_key_pair_bits" cty:"temporary_key_pair_bits" hcl:"temporary_key_pair_bits"` + SSHCiphers []string `mapstructure:"ssh_ciphers" cty:"ssh_ciphers" hcl:"ssh_ciphers"` + SSHClearAuthorizedKeys *bool `mapstructure:"ssh_clear_authorized_keys" cty:"ssh_clear_authorized_keys" hcl:"ssh_clear_authorized_keys"` + SSHKEXAlgos []string `mapstructure:"ssh_key_exchange_algorithms" cty:"ssh_key_exchange_algorithms" hcl:"ssh_key_exchange_algorithms"` + SSHPrivateKeyFile *string `mapstructure:"ssh_private_key_file" undocumented:"true" cty:"ssh_private_key_file" hcl:"ssh_private_key_file"` + SSHCertificateFile *string `mapstructure:"ssh_certificate_file" cty:"ssh_certificate_file" hcl:"ssh_certificate_file"` + SSHPty *bool `mapstructure:"ssh_pty" cty:"ssh_pty" hcl:"ssh_pty"` + SSHTimeout *string `mapstructure:"ssh_timeout" cty:"ssh_timeout" hcl:"ssh_timeout"` + SSHWaitTimeout *string `mapstructure:"ssh_wait_timeout" undocumented:"true" cty:"ssh_wait_timeout" hcl:"ssh_wait_timeout"` + SSHAgentAuth *bool `mapstructure:"ssh_agent_auth" undocumented:"true" cty:"ssh_agent_auth" hcl:"ssh_agent_auth"` + SSHDisableAgentForwarding *bool `mapstructure:"ssh_disable_agent_forwarding" cty:"ssh_disable_agent_forwarding" hcl:"ssh_disable_agent_forwarding"` + SSHHandshakeAttempts *int `mapstructure:"ssh_handshake_attempts" cty:"ssh_handshake_attempts" hcl:"ssh_handshake_attempts"` + SSHBastionHost *string `mapstructure:"ssh_bastion_host" cty:"ssh_bastion_host" hcl:"ssh_bastion_host"` + SSHBastionPort *int `mapstructure:"ssh_bastion_port" cty:"ssh_bastion_port" hcl:"ssh_bastion_port"` + SSHBastionAgentAuth *bool `mapstructure:"ssh_bastion_agent_auth" cty:"ssh_bastion_agent_auth" hcl:"ssh_bastion_agent_auth"` + SSHBastionUsername *string `mapstructure:"ssh_bastion_username" cty:"ssh_bastion_username" hcl:"ssh_bastion_username"` + SSHBastionPassword *string `mapstructure:"ssh_bastion_password" cty:"ssh_bastion_password" hcl:"ssh_bastion_password"` + SSHBastionInteractive *bool `mapstructure:"ssh_bastion_interactive" cty:"ssh_bastion_interactive" hcl:"ssh_bastion_interactive"` + SSHBastionPrivateKeyFile *string `mapstructure:"ssh_bastion_private_key_file" cty:"ssh_bastion_private_key_file" hcl:"ssh_bastion_private_key_file"` + SSHBastionCertificateFile *string `mapstructure:"ssh_bastion_certificate_file" cty:"ssh_bastion_certificate_file" hcl:"ssh_bastion_certificate_file"` + SSHFileTransferMethod *string `mapstructure:"ssh_file_transfer_method" cty:"ssh_file_transfer_method" hcl:"ssh_file_transfer_method"` + SSHProxyHost *string `mapstructure:"ssh_proxy_host" cty:"ssh_proxy_host" hcl:"ssh_proxy_host"` + SSHProxyPort *int `mapstructure:"ssh_proxy_port" cty:"ssh_proxy_port" hcl:"ssh_proxy_port"` + SSHProxyUsername *string `mapstructure:"ssh_proxy_username" cty:"ssh_proxy_username" hcl:"ssh_proxy_username"` + SSHProxyPassword *string `mapstructure:"ssh_proxy_password" cty:"ssh_proxy_password" hcl:"ssh_proxy_password"` + SSHKeepAliveInterval *string `mapstructure:"ssh_keep_alive_interval" cty:"ssh_keep_alive_interval" hcl:"ssh_keep_alive_interval"` + SSHReadWriteTimeout *string `mapstructure:"ssh_read_write_timeout" cty:"ssh_read_write_timeout" hcl:"ssh_read_write_timeout"` + SSHRemoteTunnels []string `mapstructure:"ssh_remote_tunnels" cty:"ssh_remote_tunnels" hcl:"ssh_remote_tunnels"` + SSHLocalTunnels []string `mapstructure:"ssh_local_tunnels" cty:"ssh_local_tunnels" hcl:"ssh_local_tunnels"` + SSHPublicKey []byte `mapstructure:"ssh_public_key" undocumented:"true" cty:"ssh_public_key" hcl:"ssh_public_key"` + SSHPrivateKey []byte `mapstructure:"ssh_private_key" undocumented:"true" cty:"ssh_private_key" hcl:"ssh_private_key"` + WinRMUser *string `mapstructure:"winrm_username" cty:"winrm_username" hcl:"winrm_username"` + WinRMPassword *string `mapstructure:"winrm_password" cty:"winrm_password" hcl:"winrm_password"` + WinRMHost *string `mapstructure:"winrm_host" cty:"winrm_host" hcl:"winrm_host"` + WinRMNoProxy *bool `mapstructure:"winrm_no_proxy" cty:"winrm_no_proxy" hcl:"winrm_no_proxy"` + WinRMPort *int `mapstructure:"winrm_port" cty:"winrm_port" hcl:"winrm_port"` + WinRMTimeout *string `mapstructure:"winrm_timeout" cty:"winrm_timeout" hcl:"winrm_timeout"` + WinRMUseSSL *bool `mapstructure:"winrm_use_ssl" cty:"winrm_use_ssl" hcl:"winrm_use_ssl"` + WinRMInsecure *bool `mapstructure:"winrm_insecure" cty:"winrm_insecure" hcl:"winrm_insecure"` + WinRMUseNTLM *bool `mapstructure:"winrm_use_ntlm" cty:"winrm_use_ntlm" hcl:"winrm_use_ntlm"` + HostPortMin *int `mapstructure:"host_port_min" required:"false" cty:"host_port_min" hcl:"host_port_min"` + HostPortMax *int `mapstructure:"host_port_max" required:"false" cty:"host_port_max" hcl:"host_port_max"` + SkipNatMapping *bool `mapstructure:"skip_nat_mapping" required:"false" cty:"skip_nat_mapping" hcl:"skip_nat_mapping"` + SSHHostPortMin *int `mapstructure:"ssh_host_port_min" required:"false" cty:"ssh_host_port_min" hcl:"ssh_host_port_min"` + SSHHostPortMax *int `mapstructure:"ssh_host_port_max" cty:"ssh_host_port_max" hcl:"ssh_host_port_max"` + FloppyFiles []string `mapstructure:"floppy_files" cty:"floppy_files" hcl:"floppy_files"` + FloppyDirectories []string `mapstructure:"floppy_dirs" cty:"floppy_dirs" hcl:"floppy_dirs"` + FloppyLabel *string `mapstructure:"floppy_label" cty:"floppy_label" hcl:"floppy_label"` + CDFiles []string `mapstructure:"cd_files" cty:"cd_files" hcl:"cd_files"` + CDLabel *string `mapstructure:"cd_label" cty:"cd_label" hcl:"cd_label"` + ISOSkipCache *bool `mapstructure:"iso_skip_cache" required:"false" cty:"iso_skip_cache" hcl:"iso_skip_cache"` + Accelerator *string `mapstructure:"accelerator" required:"false" cty:"accelerator" hcl:"accelerator"` + AdditionalDiskSize []string `mapstructure:"disk_additional_size" required:"false" cty:"disk_additional_size" hcl:"disk_additional_size"` + CpuCount *int `mapstructure:"cpus" required:"false" cty:"cpus" hcl:"cpus"` + Firmware *string `mapstructure:"firmware" required:"false" cty:"firmware" hcl:"firmware"` + DiskInterface *string `mapstructure:"disk_interface" required:"false" cty:"disk_interface" hcl:"disk_interface"` + DiskSize *string `mapstructure:"disk_size" required:"false" cty:"disk_size" hcl:"disk_size"` + SkipResizeDisk *bool `mapstructure:"skip_resize_disk" required:"false" cty:"skip_resize_disk" hcl:"skip_resize_disk"` + DiskCache *string `mapstructure:"disk_cache" required:"false" cty:"disk_cache" hcl:"disk_cache"` + DiskDiscard *string `mapstructure:"disk_discard" required:"false" cty:"disk_discard" hcl:"disk_discard"` + DetectZeroes *string `mapstructure:"disk_detect_zeroes" required:"false" cty:"disk_detect_zeroes" hcl:"disk_detect_zeroes"` + SkipCompaction *bool `mapstructure:"skip_compaction" required:"false" cty:"skip_compaction" hcl:"skip_compaction"` + DiskCompression *bool `mapstructure:"disk_compression" required:"false" cty:"disk_compression" hcl:"disk_compression"` + Format *string `mapstructure:"format" required:"false" cty:"format" hcl:"format"` + Headless *bool `mapstructure:"headless" required:"false" cty:"headless" hcl:"headless"` + DiskImage *bool `mapstructure:"disk_image" required:"false" cty:"disk_image" hcl:"disk_image"` + UseBackingFile *bool `mapstructure:"use_backing_file" required:"false" cty:"use_backing_file" hcl:"use_backing_file"` + MachineType *string `mapstructure:"machine_type" required:"false" cty:"machine_type" hcl:"machine_type"` + MemorySize *int `mapstructure:"memory" required:"false" cty:"memory" hcl:"memory"` + NetDevice *string `mapstructure:"net_device" required:"false" cty:"net_device" hcl:"net_device"` + NetBridge *string `mapstructure:"net_bridge" required:"false" cty:"net_bridge" hcl:"net_bridge"` + OutputDir *string `mapstructure:"output_directory" required:"false" cty:"output_directory" hcl:"output_directory"` + QemuArgs [][]string `mapstructure:"qemuargs" required:"false" cty:"qemuargs" hcl:"qemuargs"` + QemuImgArgs *FlatQemuImgArgs `mapstructure:"qemu_img_args" required:"false" cty:"qemu_img_args" hcl:"qemu_img_args"` + QemuBinary *string `mapstructure:"qemu_binary" required:"false" cty:"qemu_binary" hcl:"qemu_binary"` + QMPEnable *bool `mapstructure:"qmp_enable" required:"false" cty:"qmp_enable" hcl:"qmp_enable"` + QMPSocketPath *string `mapstructure:"qmp_socket_path" required:"false" cty:"qmp_socket_path" hcl:"qmp_socket_path"` + UseDefaultDisplay *bool `mapstructure:"use_default_display" required:"false" cty:"use_default_display" hcl:"use_default_display"` + Display *string `mapstructure:"display" required:"false" cty:"display" hcl:"display"` + VNCBindAddress *string `mapstructure:"vnc_bind_address" required:"false" cty:"vnc_bind_address" hcl:"vnc_bind_address"` + VNCUsePassword *bool `mapstructure:"vnc_use_password" required:"false" cty:"vnc_use_password" hcl:"vnc_use_password"` + VNCPortMin *int `mapstructure:"vnc_port_min" required:"false" cty:"vnc_port_min" hcl:"vnc_port_min"` + VNCPortMax *int `mapstructure:"vnc_port_max" cty:"vnc_port_max" hcl:"vnc_port_max"` + VMName *string `mapstructure:"vm_name" required:"false" cty:"vm_name" hcl:"vm_name"` + CDROMInterface *string `mapstructure:"cdrom_interface" required:"false" cty:"cdrom_interface" hcl:"cdrom_interface"` + RunOnce *bool `mapstructure:"run_once" cty:"run_once" hcl:"run_once"` +} + +// FlatMapstructure returns a new FlatConfig. +// FlatConfig is an auto-generated flat version of Config. +// Where the contents a fields with a `mapstructure:,squash` tag are bubbled up. +func (*Config) FlatMapstructure() interface{ HCL2Spec() map[string]hcldec.Spec } { + return new(FlatConfig) +} + +// HCL2Spec returns the hcl spec of a Config. +// This spec is used by HCL to read the fields of Config. +// The decoded values from this spec will then be applied to a FlatConfig. +func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec { + s := map[string]hcldec.Spec{ + "packer_build_name": &hcldec.AttrSpec{Name: "packer_build_name", Type: cty.String, Required: false}, + "packer_builder_type": &hcldec.AttrSpec{Name: "packer_builder_type", Type: cty.String, Required: false}, + "packer_core_version": &hcldec.AttrSpec{Name: "packer_core_version", Type: cty.String, Required: false}, + "packer_debug": &hcldec.AttrSpec{Name: "packer_debug", Type: cty.Bool, Required: false}, + "packer_force": &hcldec.AttrSpec{Name: "packer_force", Type: cty.Bool, Required: false}, + "packer_on_error": &hcldec.AttrSpec{Name: "packer_on_error", Type: cty.String, Required: false}, + "packer_user_variables": &hcldec.AttrSpec{Name: "packer_user_variables", Type: cty.Map(cty.String), Required: false}, + "packer_sensitive_variables": &hcldec.AttrSpec{Name: "packer_sensitive_variables", Type: cty.List(cty.String), Required: false}, + "http_directory": &hcldec.AttrSpec{Name: "http_directory", Type: cty.String, Required: false}, + "http_content": &hcldec.AttrSpec{Name: "http_content", Type: cty.Map(cty.String), Required: false}, + "http_port_min": &hcldec.AttrSpec{Name: "http_port_min", Type: cty.Number, Required: false}, + "http_port_max": &hcldec.AttrSpec{Name: "http_port_max", Type: cty.Number, Required: false}, + "http_bind_address": &hcldec.AttrSpec{Name: "http_bind_address", Type: cty.String, Required: false}, + "http_interface": &hcldec.AttrSpec{Name: "http_interface", Type: cty.String, Required: false}, + "iso_checksum": &hcldec.AttrSpec{Name: "iso_checksum", Type: cty.String, Required: false}, + "iso_url": &hcldec.AttrSpec{Name: "iso_url", Type: cty.String, Required: false}, + "iso_urls": &hcldec.AttrSpec{Name: "iso_urls", Type: cty.List(cty.String), Required: false}, + "iso_target_path": &hcldec.AttrSpec{Name: "iso_target_path", Type: cty.String, Required: false}, + "iso_target_extension": &hcldec.AttrSpec{Name: "iso_target_extension", Type: cty.String, Required: false}, + "boot_keygroup_interval": &hcldec.AttrSpec{Name: "boot_keygroup_interval", Type: cty.String, Required: false}, + "boot_wait": &hcldec.AttrSpec{Name: "boot_wait", Type: cty.String, Required: false}, + "boot_command": &hcldec.AttrSpec{Name: "boot_command", Type: cty.List(cty.String), Required: false}, + "disable_vnc": &hcldec.AttrSpec{Name: "disable_vnc", Type: cty.Bool, Required: false}, + "boot_key_interval": &hcldec.AttrSpec{Name: "boot_key_interval", Type: cty.String, Required: false}, + "shutdown_command": &hcldec.AttrSpec{Name: "shutdown_command", Type: cty.String, Required: false}, + "shutdown_timeout": &hcldec.AttrSpec{Name: "shutdown_timeout", Type: cty.String, Required: false}, + "communicator": &hcldec.AttrSpec{Name: "communicator", Type: cty.String, Required: false}, + "pause_before_connecting": &hcldec.AttrSpec{Name: "pause_before_connecting", Type: cty.String, Required: false}, + "ssh_host": &hcldec.AttrSpec{Name: "ssh_host", Type: cty.String, Required: false}, + "ssh_port": &hcldec.AttrSpec{Name: "ssh_port", Type: cty.Number, Required: false}, + "ssh_username": &hcldec.AttrSpec{Name: "ssh_username", Type: cty.String, Required: false}, + "ssh_password": &hcldec.AttrSpec{Name: "ssh_password", Type: cty.String, Required: false}, + "ssh_keypair_name": &hcldec.AttrSpec{Name: "ssh_keypair_name", Type: cty.String, Required: false}, + "temporary_key_pair_name": &hcldec.AttrSpec{Name: "temporary_key_pair_name", Type: cty.String, Required: false}, + "temporary_key_pair_type": &hcldec.AttrSpec{Name: "temporary_key_pair_type", Type: cty.String, Required: false}, + "temporary_key_pair_bits": &hcldec.AttrSpec{Name: "temporary_key_pair_bits", Type: cty.Number, Required: false}, + "ssh_ciphers": &hcldec.AttrSpec{Name: "ssh_ciphers", Type: cty.List(cty.String), Required: false}, + "ssh_clear_authorized_keys": &hcldec.AttrSpec{Name: "ssh_clear_authorized_keys", Type: cty.Bool, Required: false}, + "ssh_key_exchange_algorithms": &hcldec.AttrSpec{Name: "ssh_key_exchange_algorithms", Type: cty.List(cty.String), Required: false}, + "ssh_private_key_file": &hcldec.AttrSpec{Name: "ssh_private_key_file", Type: cty.String, Required: false}, + "ssh_certificate_file": &hcldec.AttrSpec{Name: "ssh_certificate_file", Type: cty.String, Required: false}, + "ssh_pty": &hcldec.AttrSpec{Name: "ssh_pty", Type: cty.Bool, Required: false}, + "ssh_timeout": &hcldec.AttrSpec{Name: "ssh_timeout", Type: cty.String, Required: false}, + "ssh_wait_timeout": &hcldec.AttrSpec{Name: "ssh_wait_timeout", Type: cty.String, Required: false}, + "ssh_agent_auth": &hcldec.AttrSpec{Name: "ssh_agent_auth", Type: cty.Bool, Required: false}, + "ssh_disable_agent_forwarding": &hcldec.AttrSpec{Name: "ssh_disable_agent_forwarding", Type: cty.Bool, Required: false}, + "ssh_handshake_attempts": &hcldec.AttrSpec{Name: "ssh_handshake_attempts", Type: cty.Number, Required: false}, + "ssh_bastion_host": &hcldec.AttrSpec{Name: "ssh_bastion_host", Type: cty.String, Required: false}, + "ssh_bastion_port": &hcldec.AttrSpec{Name: "ssh_bastion_port", Type: cty.Number, Required: false}, + "ssh_bastion_agent_auth": &hcldec.AttrSpec{Name: "ssh_bastion_agent_auth", Type: cty.Bool, Required: false}, + "ssh_bastion_username": &hcldec.AttrSpec{Name: "ssh_bastion_username", Type: cty.String, Required: false}, + "ssh_bastion_password": &hcldec.AttrSpec{Name: "ssh_bastion_password", Type: cty.String, Required: false}, + "ssh_bastion_interactive": &hcldec.AttrSpec{Name: "ssh_bastion_interactive", Type: cty.Bool, Required: false}, + "ssh_bastion_private_key_file": &hcldec.AttrSpec{Name: "ssh_bastion_private_key_file", Type: cty.String, Required: false}, + "ssh_bastion_certificate_file": &hcldec.AttrSpec{Name: "ssh_bastion_certificate_file", Type: cty.String, Required: false}, + "ssh_file_transfer_method": &hcldec.AttrSpec{Name: "ssh_file_transfer_method", Type: cty.String, Required: false}, + "ssh_proxy_host": &hcldec.AttrSpec{Name: "ssh_proxy_host", Type: cty.String, Required: false}, + "ssh_proxy_port": &hcldec.AttrSpec{Name: "ssh_proxy_port", Type: cty.Number, Required: false}, + "ssh_proxy_username": &hcldec.AttrSpec{Name: "ssh_proxy_username", Type: cty.String, Required: false}, + "ssh_proxy_password": &hcldec.AttrSpec{Name: "ssh_proxy_password", Type: cty.String, Required: false}, + "ssh_keep_alive_interval": &hcldec.AttrSpec{Name: "ssh_keep_alive_interval", Type: cty.String, Required: false}, + "ssh_read_write_timeout": &hcldec.AttrSpec{Name: "ssh_read_write_timeout", Type: cty.String, Required: false}, + "ssh_remote_tunnels": &hcldec.AttrSpec{Name: "ssh_remote_tunnels", Type: cty.List(cty.String), Required: false}, + "ssh_local_tunnels": &hcldec.AttrSpec{Name: "ssh_local_tunnels", Type: cty.List(cty.String), Required: false}, + "ssh_public_key": &hcldec.AttrSpec{Name: "ssh_public_key", Type: cty.List(cty.Number), Required: false}, + "ssh_private_key": &hcldec.AttrSpec{Name: "ssh_private_key", Type: cty.List(cty.Number), Required: false}, + "winrm_username": &hcldec.AttrSpec{Name: "winrm_username", Type: cty.String, Required: false}, + "winrm_password": &hcldec.AttrSpec{Name: "winrm_password", Type: cty.String, Required: false}, + "winrm_host": &hcldec.AttrSpec{Name: "winrm_host", Type: cty.String, Required: false}, + "winrm_no_proxy": &hcldec.AttrSpec{Name: "winrm_no_proxy", Type: cty.Bool, Required: false}, + "winrm_port": &hcldec.AttrSpec{Name: "winrm_port", Type: cty.Number, Required: false}, + "winrm_timeout": &hcldec.AttrSpec{Name: "winrm_timeout", Type: cty.String, Required: false}, + "winrm_use_ssl": &hcldec.AttrSpec{Name: "winrm_use_ssl", Type: cty.Bool, Required: false}, + "winrm_insecure": &hcldec.AttrSpec{Name: "winrm_insecure", Type: cty.Bool, Required: false}, + "winrm_use_ntlm": &hcldec.AttrSpec{Name: "winrm_use_ntlm", Type: cty.Bool, Required: false}, + "host_port_min": &hcldec.AttrSpec{Name: "host_port_min", Type: cty.Number, Required: false}, + "host_port_max": &hcldec.AttrSpec{Name: "host_port_max", Type: cty.Number, Required: false}, + "skip_nat_mapping": &hcldec.AttrSpec{Name: "skip_nat_mapping", Type: cty.Bool, Required: false}, + "ssh_host_port_min": &hcldec.AttrSpec{Name: "ssh_host_port_min", Type: cty.Number, Required: false}, + "ssh_host_port_max": &hcldec.AttrSpec{Name: "ssh_host_port_max", Type: cty.Number, Required: false}, + "floppy_files": &hcldec.AttrSpec{Name: "floppy_files", Type: cty.List(cty.String), Required: false}, + "floppy_dirs": &hcldec.AttrSpec{Name: "floppy_dirs", Type: cty.List(cty.String), Required: false}, + "floppy_label": &hcldec.AttrSpec{Name: "floppy_label", Type: cty.String, Required: false}, + "cd_files": &hcldec.AttrSpec{Name: "cd_files", Type: cty.List(cty.String), Required: false}, + "cd_label": &hcldec.AttrSpec{Name: "cd_label", Type: cty.String, Required: false}, + "iso_skip_cache": &hcldec.AttrSpec{Name: "iso_skip_cache", Type: cty.Bool, Required: false}, + "accelerator": &hcldec.AttrSpec{Name: "accelerator", Type: cty.String, Required: false}, + "disk_additional_size": &hcldec.AttrSpec{Name: "disk_additional_size", Type: cty.List(cty.String), Required: false}, + "cpus": &hcldec.AttrSpec{Name: "cpus", Type: cty.Number, Required: false}, + "firmware": &hcldec.AttrSpec{Name: "firmware", Type: cty.String, Required: false}, + "disk_interface": &hcldec.AttrSpec{Name: "disk_interface", Type: cty.String, Required: false}, + "disk_size": &hcldec.AttrSpec{Name: "disk_size", Type: cty.String, Required: false}, + "skip_resize_disk": &hcldec.AttrSpec{Name: "skip_resize_disk", Type: cty.Bool, Required: false}, + "disk_cache": &hcldec.AttrSpec{Name: "disk_cache", Type: cty.String, Required: false}, + "disk_discard": &hcldec.AttrSpec{Name: "disk_discard", Type: cty.String, Required: false}, + "disk_detect_zeroes": &hcldec.AttrSpec{Name: "disk_detect_zeroes", Type: cty.String, Required: false}, + "skip_compaction": &hcldec.AttrSpec{Name: "skip_compaction", Type: cty.Bool, Required: false}, + "disk_compression": &hcldec.AttrSpec{Name: "disk_compression", Type: cty.Bool, Required: false}, + "format": &hcldec.AttrSpec{Name: "format", Type: cty.String, Required: false}, + "headless": &hcldec.AttrSpec{Name: "headless", Type: cty.Bool, Required: false}, + "disk_image": &hcldec.AttrSpec{Name: "disk_image", Type: cty.Bool, Required: false}, + "use_backing_file": &hcldec.AttrSpec{Name: "use_backing_file", Type: cty.Bool, Required: false}, + "machine_type": &hcldec.AttrSpec{Name: "machine_type", Type: cty.String, Required: false}, + "memory": &hcldec.AttrSpec{Name: "memory", Type: cty.Number, Required: false}, + "net_device": &hcldec.AttrSpec{Name: "net_device", Type: cty.String, Required: false}, + "net_bridge": &hcldec.AttrSpec{Name: "net_bridge", Type: cty.String, Required: false}, + "output_directory": &hcldec.AttrSpec{Name: "output_directory", Type: cty.String, Required: false}, + "qemuargs": &hcldec.AttrSpec{Name: "qemuargs", Type: cty.List(cty.List(cty.String)), Required: false}, + "qemu_img_args": &hcldec.BlockSpec{TypeName: "qemu_img_args", Nested: hcldec.ObjectSpec((*FlatQemuImgArgs)(nil).HCL2Spec())}, + "qemu_binary": &hcldec.AttrSpec{Name: "qemu_binary", Type: cty.String, Required: false}, + "qmp_enable": &hcldec.AttrSpec{Name: "qmp_enable", Type: cty.Bool, Required: false}, + "qmp_socket_path": &hcldec.AttrSpec{Name: "qmp_socket_path", Type: cty.String, Required: false}, + "use_default_display": &hcldec.AttrSpec{Name: "use_default_display", Type: cty.Bool, Required: false}, + "display": &hcldec.AttrSpec{Name: "display", Type: cty.String, Required: false}, + "vnc_bind_address": &hcldec.AttrSpec{Name: "vnc_bind_address", Type: cty.String, Required: false}, + "vnc_use_password": &hcldec.AttrSpec{Name: "vnc_use_password", Type: cty.Bool, Required: false}, + "vnc_port_min": &hcldec.AttrSpec{Name: "vnc_port_min", Type: cty.Number, Required: false}, + "vnc_port_max": &hcldec.AttrSpec{Name: "vnc_port_max", Type: cty.Number, Required: false}, + "vm_name": &hcldec.AttrSpec{Name: "vm_name", Type: cty.String, Required: false}, + "cdrom_interface": &hcldec.AttrSpec{Name: "cdrom_interface", Type: cty.String, Required: false}, + "run_once": &hcldec.AttrSpec{Name: "run_once", Type: cty.Bool, Required: false}, + } + return s +} + +// FlatQemuImgArgs is an auto-generated flat version of QemuImgArgs. +// Where the contents of a field with a `mapstructure:,squash` tag are bubbled up. +type FlatQemuImgArgs struct { + Convert []string `mapstructure:"convert" required:"false" cty:"convert" hcl:"convert"` + Create []string `mapstructure:"create" required:"false" cty:"create" hcl:"create"` + Resize []string `mapstructure:"resize" required:"false" cty:"resize" hcl:"resize"` +} + +// FlatMapstructure returns a new FlatQemuImgArgs. +// FlatQemuImgArgs is an auto-generated flat version of QemuImgArgs. +// Where the contents a fields with a `mapstructure:,squash` tag are bubbled up. +func (*QemuImgArgs) FlatMapstructure() interface{ HCL2Spec() map[string]hcldec.Spec } { + return new(FlatQemuImgArgs) +} + +// HCL2Spec returns the hcl spec of a QemuImgArgs. +// This spec is used by HCL to read the fields of QemuImgArgs. +// The decoded values from this spec will then be applied to a FlatQemuImgArgs. +func (*FlatQemuImgArgs) HCL2Spec() map[string]hcldec.Spec { + s := map[string]hcldec.Spec{ + "convert": &hcldec.AttrSpec{Name: "convert", Type: cty.List(cty.String), Required: false}, + "create": &hcldec.AttrSpec{Name: "create", Type: cty.List(cty.String), Required: false}, + "resize": &hcldec.AttrSpec{Name: "resize", Type: cty.List(cty.String), Required: false}, + } + return s +} diff --git a/vendor/github.com/hashicorp/packer-plugin-qemu/builder/qemu/driver.go b/vendor/github.com/hashicorp/packer-plugin-qemu/builder/qemu/driver.go new file mode 100644 index 000000000..3cbcf6ecf --- /dev/null +++ b/vendor/github.com/hashicorp/packer-plugin-qemu/builder/qemu/driver.go @@ -0,0 +1,247 @@ +package qemu + +import ( + "bufio" + "bytes" + "fmt" + "io" + "log" + "os" + "os/exec" + "regexp" + "strings" + "sync" + "syscall" + "time" + "unicode" + + "github.com/hashicorp/packer-plugin-sdk/multistep" +) + +type DriverCancelCallback func(state multistep.StateBag) bool + +// A driver is able to talk to qemu-system-x86_64 and perform certain +// operations with it. +type Driver interface { + // Copy bypasses qemu-img convert and directly copies an image + // that doesn't need converting. + Copy(string, string) error + + // Stop stops a running machine, forcefully. + Stop() error + + // Qemu executes the given command via qemu-system-x86_64 + Qemu(qemuArgs ...string) error + + // wait on shutdown of the VM with option to cancel + WaitForShutdown(<-chan struct{}) bool + + // Qemu executes the given command via qemu-img + QemuImg(...string) error + + // Verify checks to make sure that this driver should function + // properly. If there is any indication the driver can't function, + // this will return an error. + Verify() error + + // Version reads the version of Qemu that is installed. + Version() (string, error) +} + +type QemuDriver struct { + QemuPath string + QemuImgPath string + + vmCmd *exec.Cmd + vmEndCh <-chan int + lock sync.Mutex +} + +func (d *QemuDriver) Stop() error { + d.lock.Lock() + defer d.lock.Unlock() + + if d.vmCmd != nil { + if err := d.vmCmd.Process.Kill(); err != nil { + return err + } + } + + return nil +} + +func (d *QemuDriver) Copy(sourceName, targetName string) error { + source, err := os.Open(sourceName) + if err != nil { + err = fmt.Errorf("Error opening iso for copy: %s", err) + return err + } + defer source.Close() + + // Create will truncate an existing file + target, err := os.Create(targetName) + if err != nil { + err = fmt.Errorf("Error creating hard drive in output dir: %s", err) + return err + } + defer target.Close() + + log.Printf("Copying %s to %s", source.Name(), target.Name()) + bytes, err := io.Copy(target, source) + if err != nil { + err = fmt.Errorf("Error copying iso to output dir: %s", err) + return err + } + log.Printf(fmt.Sprintf("Copied %d bytes", bytes)) + + return nil +} + +func (d *QemuDriver) Qemu(qemuArgs ...string) error { + d.lock.Lock() + defer d.lock.Unlock() + + if d.vmCmd != nil { + panic("Existing VM state found") + } + + stdout_r, stdout_w := io.Pipe() + stderr_r, stderr_w := io.Pipe() + + log.Printf("Executing %s: %#v", d.QemuPath, qemuArgs) + cmd := exec.Command(d.QemuPath, qemuArgs...) + cmd.Stdout = stdout_w + cmd.Stderr = stderr_w + + err := cmd.Start() + if err != nil { + err = fmt.Errorf("Error starting VM: %s", err) + return err + } + + go logReader("Qemu stdout", stdout_r) + go logReader("Qemu stderr", stderr_r) + + log.Printf("Started Qemu. Pid: %d", cmd.Process.Pid) + + // Wait for Qemu to complete in the background, and mark when its done + endCh := make(chan int, 1) + go func() { + defer stderr_w.Close() + defer stdout_w.Close() + + var exitCode int = 0 + if err := cmd.Wait(); err != nil { + if exiterr, ok := err.(*exec.ExitError); ok { + // The program has exited with an exit code != 0 + if status, ok := exiterr.Sys().(syscall.WaitStatus); ok { + exitCode = status.ExitStatus() + } else { + exitCode = 254 + } + } + } + + endCh <- exitCode + + d.lock.Lock() + defer d.lock.Unlock() + d.vmCmd = nil + d.vmEndCh = nil + }() + + // Wait at least a couple seconds for an early fail from Qemu so + // we can report that. + select { + case exit := <-endCh: + if exit != 0 { + return fmt.Errorf("Qemu failed to start. Please run with PACKER_LOG=1 to get more info.") + } + case <-time.After(2 * time.Second): + } + + // Setup our state so we know we are running + d.vmCmd = cmd + d.vmEndCh = endCh + + return nil +} + +func (d *QemuDriver) WaitForShutdown(cancelCh <-chan struct{}) bool { + d.lock.Lock() + endCh := d.vmEndCh + d.lock.Unlock() + + if endCh == nil { + return true + } + + select { + case <-endCh: + return true + case <-cancelCh: + return false + } +} + +func (d *QemuDriver) QemuImg(args ...string) error { + var stdout, stderr bytes.Buffer + + log.Printf("Executing qemu-img: %#v", args) + cmd := exec.Command(d.QemuImgPath, args...) + cmd.Stdout = &stdout + cmd.Stderr = &stderr + err := cmd.Run() + + stdoutString := strings.TrimSpace(stdout.String()) + stderrString := strings.TrimSpace(stderr.String()) + + if _, ok := err.(*exec.ExitError); ok { + err = fmt.Errorf("QemuImg error: %s", stderrString) + } + + log.Printf("stdout: %s", stdoutString) + log.Printf("stderr: %s", stderrString) + + return err +} + +func (d *QemuDriver) Verify() error { + return nil +} + +func (d *QemuDriver) Version() (string, error) { + var stdout bytes.Buffer + + cmd := exec.Command(d.QemuPath, "-version") + cmd.Stdout = &stdout + if err := cmd.Run(); err != nil { + return "", err + } + + versionOutput := strings.TrimSpace(stdout.String()) + log.Printf("Qemu --version output: %s", versionOutput) + versionRe := regexp.MustCompile(`[\.[0-9]+]*`) + matches := versionRe.FindStringSubmatch(versionOutput) + if len(matches) == 0 { + return "", fmt.Errorf("No version found: %s", versionOutput) + } + + log.Printf("Qemu version: %s", matches[0]) + return matches[0], nil +} + +func logReader(name string, r io.Reader) { + bufR := bufio.NewReader(r) + for { + line, err := bufR.ReadString('\n') + if line != "" { + line = strings.TrimRightFunc(line, unicode.IsSpace) + log.Printf("%s: %s", name, line) + } + + if err == io.EOF { + break + } + } +} diff --git a/vendor/github.com/hashicorp/packer-plugin-qemu/builder/qemu/driver_mock.go b/vendor/github.com/hashicorp/packer-plugin-qemu/builder/qemu/driver_mock.go new file mode 100644 index 000000000..7fa1a7df7 --- /dev/null +++ b/vendor/github.com/hashicorp/packer-plugin-qemu/builder/qemu/driver_mock.go @@ -0,0 +1,74 @@ +package qemu + +import "sync" + +type DriverMock struct { + sync.Mutex + + CopyCalled bool + CopyErr error + + StopCalled bool + StopErr error + + QemuCalls [][]string + QemuErrs []error + + WaitForShutdownCalled bool + WaitForShutdownState bool + + QemuImgCalled bool + QemuImgCalls []string + QemuImgErrs []error + + VerifyCalled bool + VerifyErr error + + VersionCalled bool + VersionResult string + VersionErr error +} + +func (d *DriverMock) Copy(source, dst string) error { + d.CopyCalled = true + return d.CopyErr +} + +func (d *DriverMock) Stop() error { + d.StopCalled = true + return d.StopErr +} + +func (d *DriverMock) Qemu(args ...string) error { + d.QemuCalls = append(d.QemuCalls, args) + + if len(d.QemuErrs) >= len(d.QemuCalls) { + return d.QemuErrs[len(d.QemuCalls)-1] + } + return nil +} + +func (d *DriverMock) WaitForShutdown(cancelCh <-chan struct{}) bool { + d.WaitForShutdownCalled = true + return d.WaitForShutdownState +} + +func (d *DriverMock) QemuImg(args ...string) error { + d.QemuImgCalled = true + d.QemuImgCalls = append(d.QemuImgCalls, args...) + + if len(d.QemuImgErrs) >= len(d.QemuImgCalls) { + return d.QemuImgErrs[len(d.QemuImgCalls)-1] + } + return nil +} + +func (d *DriverMock) Verify() error { + d.VerifyCalled = true + return d.VerifyErr +} + +func (d *DriverMock) Version() (string, error) { + d.VersionCalled = true + return d.VersionResult, d.VersionErr +} diff --git a/vendor/github.com/hashicorp/packer-plugin-qemu/builder/qemu/qmp.go b/vendor/github.com/hashicorp/packer-plugin-qemu/builder/qemu/qmp.go new file mode 100644 index 000000000..04c4bea14 --- /dev/null +++ b/vendor/github.com/hashicorp/packer-plugin-qemu/builder/qemu/qmp.go @@ -0,0 +1,135 @@ +package qemu + +import ( + "encoding/json" + "fmt" + "strings" + + "github.com/digitalocean/go-qemu/qmp" +) + +type qomListRequest struct { + Execute string `json:"execute"` + Arguments qomListRequestArguments `json:"arguments"` +} + +type qomListRequestArguments struct { + Path string `json:"path"` +} + +type qomListResponse struct { + Return []qomListReturn `json:"return"` +} + +type qomListReturn struct { + Name string `json:"name"` + Type string `json:"type"` +} + +func qmpQomList(qmpMonitor *qmp.SocketMonitor, path string) ([]qomListReturn, error) { + request, _ := json.Marshal(qomListRequest{ + Execute: "qom-list", + Arguments: qomListRequestArguments{ + Path: path, + }, + }) + result, err := qmpMonitor.Run(request) + if err != nil { + return nil, err + } + var response qomListResponse + if err := json.Unmarshal(result, &response); err != nil { + return nil, err + } + return response.Return, nil +} + +type qomGetRequest struct { + Execute string `json:"execute"` + Arguments qomGetRequestArguments `json:"arguments"` +} + +type qomGetRequestArguments struct { + Path string `json:"path"` + Property string `json:"property"` +} + +type qomGetResponse struct { + Return string `json:"return"` +} + +func qmpQomGet(qmpMonitor *qmp.SocketMonitor, path string, property string) (string, error) { + request, _ := json.Marshal(qomGetRequest{ + Execute: "qom-get", + Arguments: qomGetRequestArguments{ + Path: path, + Property: property, + }, + }) + result, err := qmpMonitor.Run(request) + if err != nil { + return "", err + } + var response qomGetResponse + if err := json.Unmarshal(result, &response); err != nil { + return "", err + } + return response.Return, nil +} + +type netDevice struct { + Path string + Name string + Type string + MacAddress string +} + +func getNetDevices(qmpMonitor *qmp.SocketMonitor) ([]netDevice, error) { + devices := []netDevice{} + for _, parentPath := range []string{"/machine/peripheral", "/machine/peripheral-anon"} { + listResponse, err := qmpQomList(qmpMonitor, parentPath) + if err != nil { + return nil, fmt.Errorf("failed to get qmp qom list %v: %w", parentPath, err) + } + for _, p := range listResponse { + if strings.HasPrefix(p.Type, "child<") { + path := fmt.Sprintf("%s/%s", parentPath, p.Name) + r, err := qmpQomList(qmpMonitor, path) + if err != nil { + return nil, fmt.Errorf("failed to get qmp qom list %v: %w", path, err) + } + isNetdev := false + for _, d := range r { + if d.Name == "netdev" { + isNetdev = true + break + } + } + if isNetdev { + device := netDevice{ + Path: path, + } + for _, d := range r { + if d.Name != "type" && d.Name != "netdev" && d.Name != "mac" { + continue + } + value, err := qmpQomGet(qmpMonitor, path, d.Name) + if err != nil { + return nil, fmt.Errorf("failed to get qmp qom property %v %v: %w", path, d.Name, err) + } + switch d.Name { + case "type": + device.Type = value + case "netdev": + device.Name = value + case "mac": + device.MacAddress = value + } + } + devices = append(devices, device) + } + } + } + } + return devices, nil +} diff --git a/vendor/github.com/hashicorp/packer-plugin-qemu/builder/qemu/ssh.go b/vendor/github.com/hashicorp/packer-plugin-qemu/builder/qemu/ssh.go new file mode 100644 index 000000000..62ed86a41 --- /dev/null +++ b/vendor/github.com/hashicorp/packer-plugin-qemu/builder/qemu/ssh.go @@ -0,0 +1,30 @@ +package qemu + +import ( + "log" + + "github.com/hashicorp/packer-plugin-sdk/multistep" +) + +func commHost(host string) func(multistep.StateBag) (string, error) { + return func(state multistep.StateBag) (string, error) { + if host != "" { + log.Printf("Using host value: %s", host) + return host, nil + } + + if guestAddress, ok := state.Get("guestAddress").(string); ok { + return guestAddress, nil + } + + return "127.0.0.1", nil + } +} + +func commPort(state multistep.StateBag) (int, error) { + commHostPort, ok := state.Get("commHostPort").(int) + if !ok { + commHostPort = 22 + } + return commHostPort, nil +} diff --git a/vendor/github.com/hashicorp/packer-plugin-qemu/builder/qemu/step_configure_qmp.go b/vendor/github.com/hashicorp/packer-plugin-qemu/builder/qemu/step_configure_qmp.go new file mode 100644 index 000000000..ccd867d6e --- /dev/null +++ b/vendor/github.com/hashicorp/packer-plugin-qemu/builder/qemu/step_configure_qmp.go @@ -0,0 +1,92 @@ +package qemu + +import ( + "context" + "fmt" + "log" + "os" + "time" + + "github.com/digitalocean/go-qemu/qmp" + "github.com/hashicorp/packer-plugin-sdk/multistep" + packersdk "github.com/hashicorp/packer-plugin-sdk/packer" +) + +// This step configures the VM to enable the QMP listener. +// +// Uses: +// config *config +// ui packersdk.Ui +// +// Produces: +type stepConfigureQMP struct { + monitor *qmp.SocketMonitor + QMPSocketPath string +} + +func (s *stepConfigureQMP) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction { + config := state.Get("config").(*Config) + ui := state.Get("ui").(packersdk.Ui) + + if !config.QMPEnable { + return multistep.ActionContinue + } + + ui.Say(fmt.Sprintf("QMP socket at: %s", s.QMPSocketPath)) + + // Only initialize and open QMP when we have a use for it. + // Open QMP socket + var err error + var cmd []byte + var result []byte + s.monitor, err = qmp.NewSocketMonitor("unix", s.QMPSocketPath, 2*time.Second) + if err != nil { + err := fmt.Errorf("Error opening QMP socket: %s", err) + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } + + // Connect to QMP + // function automatically calls capabilities so is immediately ready for commands + err = s.monitor.Connect() + if err != nil { + err := fmt.Errorf("Error connecting to QMP socket: %s", err) + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } + log.Printf("QMP socket open SUCCESS") + + vncPassword := state.Get("vnc_password") + if vncPassword != "" { + cmd = []byte(fmt.Sprintf("{ \"execute\": \"change-vnc-password\", \"arguments\": { \"password\": \"%s\" } }", + vncPassword)) + result, err = s.monitor.Run(cmd) + if err != nil { + err := fmt.Errorf("Error connecting to QMP socket: %s", err) + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } + log.Printf("QMP Command: %s\nResult: %s", cmd, result) + } + + // make the qmp_monitor available to other steps. + state.Put("qmp_monitor", s.monitor) + + return multistep.ActionContinue +} + +func (s *stepConfigureQMP) Cleanup(multistep.StateBag) { + if s.monitor != nil { + err := s.monitor.Disconnect() + if err != nil { + log.Printf("failed to disconnect QMP: %v", err) + } + // Delete file associated with qmp socket. + if err := os.Remove(s.QMPSocketPath); err != nil { + log.Printf("Failed to delete the qmp socket file: %s", err) + } + } +} diff --git a/vendor/github.com/hashicorp/packer-plugin-qemu/builder/qemu/step_configure_vnc.go b/vendor/github.com/hashicorp/packer-plugin-qemu/builder/qemu/step_configure_vnc.go new file mode 100644 index 000000000..776958c37 --- /dev/null +++ b/vendor/github.com/hashicorp/packer-plugin-qemu/builder/qemu/step_configure_vnc.go @@ -0,0 +1,89 @@ +package qemu + +import ( + "context" + "fmt" + "log" + "math/rand" + + "github.com/hashicorp/packer-plugin-sdk/multistep" + "github.com/hashicorp/packer-plugin-sdk/net" + packersdk "github.com/hashicorp/packer-plugin-sdk/packer" +) + +// This step configures the VM to enable the VNC server. +// +// Uses: +// config *config +// ui packersdk.Ui +// +// Produces: +// vnc_port int - The port that VNC is configured to listen on. +type stepConfigureVNC struct { + l *net.Listener +} + +func VNCPassword() string { + length := int(8) + + charSet := []byte("012345689abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ") + charSetLength := len(charSet) + + password := make([]byte, length) + + for i := 0; i < length; i++ { + password[i] = charSet[rand.Intn(charSetLength)] + } + + return string(password) +} + +func (s *stepConfigureVNC) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction { + config := state.Get("config").(*Config) + ui := state.Get("ui").(packersdk.Ui) + + // Find an open VNC port. Note that this can still fail later on + // because we have to release the port at some point. But this does its + // best. + msg := fmt.Sprintf("Looking for available port between %d and %d on %s", config.VNCPortMin, config.VNCPortMax, config.VNCBindAddress) + ui.Say(msg) + log.Print(msg) + + var vncPassword string + var err error + s.l, err = net.ListenRangeConfig{ + Addr: config.VNCBindAddress, + Min: config.VNCPortMin, + Max: config.VNCPortMax, + Network: "tcp", + }.Listen(ctx) + if err != nil { + err := fmt.Errorf("Error finding VNC port: %s", err) + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } + s.l.Listener.Close() // free port, but don't unlock lock file + vncPort := s.l.Port + + if config.VNCUsePassword { + vncPassword = VNCPassword() + } else { + vncPassword = "" + } + + log.Printf("Found available VNC port: %d on IP: %s", vncPort, config.VNCBindAddress) + state.Put("vnc_port", vncPort) + state.Put("vnc_password", vncPassword) + + return multistep.ActionContinue +} + +func (s *stepConfigureVNC) Cleanup(multistep.StateBag) { + if s.l != nil { + err := s.l.Close() + if err != nil { + log.Printf("failed to unlock port lockfile: %v", err) + } + } +} diff --git a/vendor/github.com/hashicorp/packer-plugin-qemu/builder/qemu/step_convert_disk.go b/vendor/github.com/hashicorp/packer-plugin-qemu/builder/qemu/step_convert_disk.go new file mode 100644 index 000000000..0d16ed982 --- /dev/null +++ b/vendor/github.com/hashicorp/packer-plugin-qemu/builder/qemu/step_convert_disk.go @@ -0,0 +1,104 @@ +package qemu + +import ( + "context" + "fmt" + "path/filepath" + "strings" + "time" + + "github.com/hashicorp/packer-plugin-sdk/multistep" + packersdk "github.com/hashicorp/packer-plugin-sdk/packer" + "github.com/hashicorp/packer-plugin-sdk/retry" + + "os" +) + +// This step converts the virtual disk that was used as the +// hard drive for the virtual machine. +type stepConvertDisk struct { + DiskCompression bool + Format string + OutputDir string + SkipCompaction bool + VMName string + + QemuImgArgs QemuImgArgs +} + +func (s *stepConvertDisk) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction { + driver := state.Get("driver").(Driver) + ui := state.Get("ui").(packersdk.Ui) + + diskName := s.VMName + + if s.SkipCompaction && !s.DiskCompression { + return multistep.ActionContinue + } + + name := diskName + ".convert" + + sourcePath := filepath.Join(s.OutputDir, diskName) + targetPath := filepath.Join(s.OutputDir, name) + + command := s.buildConvertCommand(sourcePath, targetPath) + + ui.Say("Converting hard drive...") + // Retry the conversion a few times in case it takes the qemu process a + // moment to release the lock + err := retry.Config{ + Tries: 10, + ShouldRetry: func(err error) bool { + if strings.Contains(err.Error(), `Failed to get shared "write" lock`) { + ui.Say("Error getting file lock for conversion; retrying...") + return true + } + return false + }, + RetryDelay: (&retry.Backoff{InitialBackoff: 1 * time.Second, MaxBackoff: 10 * time.Second, Multiplier: 2}).Linear, + }.Run(ctx, func(ctx context.Context) error { + return driver.QemuImg(command...) + }) + + if err != nil { + switch err.(type) { + case *retry.RetryExhaustedError: + err = fmt.Errorf("Exhausted retries for getting file lock: %s", err) + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + default: + err := fmt.Errorf("Error converting hard drive: %s", err) + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } + } + + if err := os.Rename(targetPath, sourcePath); err != nil { + err := fmt.Errorf("Error moving converted hard drive: %s", err) + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } + + return multistep.ActionContinue +} + +func (s *stepConvertDisk) buildConvertCommand(sourcePath, targetPath string) []string { + command := []string{"convert"} + + if s.DiskCompression { + command = append(command, "-c") + } + + // Add user-provided convert args + command = append(command, s.QemuImgArgs.Convert...) + + // Add format, and paths. + command = append(command, "-O", s.Format, sourcePath, targetPath) + + return command +} + +func (s *stepConvertDisk) Cleanup(state multistep.StateBag) {} diff --git a/vendor/github.com/hashicorp/packer-plugin-qemu/builder/qemu/step_copy_disk.go b/vendor/github.com/hashicorp/packer-plugin-qemu/builder/qemu/step_copy_disk.go new file mode 100644 index 000000000..3fbc4e66b --- /dev/null +++ b/vendor/github.com/hashicorp/packer-plugin-qemu/builder/qemu/step_copy_disk.go @@ -0,0 +1,78 @@ +package qemu + +import ( + "context" + "fmt" + "path/filepath" + + "github.com/hashicorp/packer-plugin-sdk/multistep" + packersdk "github.com/hashicorp/packer-plugin-sdk/packer" +) + +// This step copies the virtual disk that will be used as the +// hard drive for the virtual machine. +type stepCopyDisk struct { + DiskImage bool + Format string + OutputDir string + UseBackingFile bool + VMName string + + QemuImgArgs QemuImgArgs +} + +func (s *stepCopyDisk) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction { + driver := state.Get("driver").(Driver) + isoPath := state.Get("iso_path").(string) + ui := state.Get("ui").(packersdk.Ui) + path := filepath.Join(s.OutputDir, s.VMName) + + // Only make a copy of the ISO as the 'main' or 'default' disk if is a disk + // image and not using a backing file. The create disk step (step_create_disk.go) + // would already have made the disk otherwise + if !s.DiskImage || s.UseBackingFile { + return multistep.ActionContinue + } + + // In some cases, the file formats provided are equivalent by comparing the + // file extensions. Skip the conversion step + // This also serves as a workaround for a QEMU bug: https://bugs.launchpad.net/qemu/+bug/1776920 + ext := filepath.Ext(isoPath) + if len(ext) >= 1 && ext[1:] == s.Format && len(s.QemuImgArgs.Convert) == 0 { + ui.Message("File extension already matches desired output format. " + + "Skipping qemu-img convert step") + err := driver.Copy(isoPath, path) + if err != nil { + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } + return multistep.ActionContinue + } + + command := s.buildConvertCommand(isoPath, path) + + ui.Say("Copying hard drive...") + if err := driver.QemuImg(command...); err != nil { + err := fmt.Errorf("Error creating hard drive: %s", err) + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } + + return multistep.ActionContinue +} + +func (s *stepCopyDisk) buildConvertCommand(sourcePath, targetPath string) []string { + command := []string{"convert"} + + // Add user-provided convert args + command = append(command, s.QemuImgArgs.Convert...) + + // Add format, and paths. + command = append(command, "-O", s.Format, sourcePath, targetPath) + + return command +} + +func (s *stepCopyDisk) Cleanup(state multistep.StateBag) {} diff --git a/vendor/github.com/hashicorp/packer-plugin-qemu/builder/qemu/step_create_disk.go b/vendor/github.com/hashicorp/packer-plugin-qemu/builder/qemu/step_create_disk.go new file mode 100644 index 000000000..e86ee49dc --- /dev/null +++ b/vendor/github.com/hashicorp/packer-plugin-qemu/builder/qemu/step_create_disk.go @@ -0,0 +1,91 @@ +package qemu + +import ( + "context" + "fmt" + "log" + "path/filepath" + + "github.com/hashicorp/packer-plugin-sdk/multistep" + packersdk "github.com/hashicorp/packer-plugin-sdk/packer" +) + +// This step creates the virtual disk that will be used as the +// hard drive for the virtual machine. +type stepCreateDisk struct { + AdditionalDiskSize []string + DiskImage bool + DiskSize string + Format string + OutputDir string + UseBackingFile bool + VMName string + QemuImgArgs QemuImgArgs +} + +func (s *stepCreateDisk) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction { + driver := state.Get("driver").(Driver) + ui := state.Get("ui").(packersdk.Ui) + name := s.VMName + + if len(s.AdditionalDiskSize) > 0 || s.UseBackingFile { + ui.Say("Creating required virtual machine disks") + } + + // The 'main' or 'default' disk + diskFullPaths := []string{filepath.Join(s.OutputDir, name)} + diskSizes := []string{s.DiskSize} + + // Additional disks + if len(s.AdditionalDiskSize) > 0 { + for i, diskSize := range s.AdditionalDiskSize { + path := filepath.Join(s.OutputDir, fmt.Sprintf("%s-%d", name, i+1)) + diskFullPaths = append(diskFullPaths, path) + diskSizes = append(diskSizes, diskSize) + } + } + + // Create all required disks + for i, diskFullPath := range diskFullPaths { + if s.DiskImage && !s.UseBackingFile && i == 0 { + // Let the copy disk step (step_copy_disk.go) create the 'main' or + // 'default' disk. + continue + } + log.Printf("[INFO] Creating disk with Path: %s and Size: %s", diskFullPath, diskSizes[i]) + + command := s.buildCreateCommand(diskFullPath, diskSizes[i], i, state) + + if err := driver.QemuImg(command...); err != nil { + err := fmt.Errorf("Error creating hard drive: %s", err) + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } + } + + // Stash the disk paths so we can retrieve later + state.Put("qemu_disk_paths", diskFullPaths) + + return multistep.ActionContinue +} + +func (s *stepCreateDisk) buildCreateCommand(path string, size string, i int, state multistep.StateBag) []string { + command := []string{"create", "-f", s.Format} + + if s.DiskImage && s.UseBackingFile && i == 0 { + // Use a backing file for the 'main' or 'default' disk + isoPath := state.Get("iso_path").(string) + command = append(command, "-b", isoPath) + } + + // add user-provided convert args + command = append(command, s.QemuImgArgs.Create...) + + // add target path and size. + command = append(command, path, size) + + return command +} + +func (s *stepCreateDisk) Cleanup(state multistep.StateBag) {} diff --git a/vendor/github.com/hashicorp/packer-plugin-qemu/builder/qemu/step_http_ip_discover.go b/vendor/github.com/hashicorp/packer-plugin-qemu/builder/qemu/step_http_ip_discover.go new file mode 100644 index 000000000..543fc111f --- /dev/null +++ b/vendor/github.com/hashicorp/packer-plugin-qemu/builder/qemu/step_http_ip_discover.go @@ -0,0 +1,68 @@ +package qemu + +import ( + "context" + "fmt" + "net" + + "github.com/hashicorp/packer-plugin-sdk/multistep" + packersdk "github.com/hashicorp/packer-plugin-sdk/packer" +) + +// Step to discover the http ip +// which guests use to reach the vm host +// To make sure the IP is set before boot command and http server steps +type stepHTTPIPDiscover struct{} + +func (s *stepHTTPIPDiscover) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction { + config := state.Get("config").(*Config) + ui := state.Get("ui").(packersdk.Ui) + + hostIP := "" + + if config.NetBridge == "" { + hostIP = "10.0.2.2" + } else { + bridgeInterface, err := net.InterfaceByName(config.NetBridge) + if err != nil { + err := fmt.Errorf("Error getting the bridge %s interface: %s", config.NetBridge, err) + ui.Error(err.Error()) + return multistep.ActionHalt + } + addrs, err := bridgeInterface.Addrs() + if err != nil { + err := fmt.Errorf("Error getting the bridge %s interface addresses: %s", config.NetBridge, err) + ui.Error(err.Error()) + return multistep.ActionHalt + } + for _, addr := range addrs { + var ip net.IP + switch v := addr.(type) { + case *net.IPNet: + ip = v.IP + case *net.IPAddr: + ip = v.IP + } + if ip == nil { + continue + } + ip = ip.To4() + if ip == nil { + continue + } + hostIP = ip.String() + break + } + if hostIP == "" { + err := fmt.Errorf("Error getting an IPv4 address from the bridge %s: cannot find any IPv4 address", config.NetBridge) + ui.Error(err.Error()) + return multistep.ActionHalt + } + } + + state.Put("http_ip", hostIP) + + return multistep.ActionContinue +} + +func (s *stepHTTPIPDiscover) Cleanup(state multistep.StateBag) {} diff --git a/vendor/github.com/hashicorp/packer-plugin-qemu/builder/qemu/step_port_forward.go b/vendor/github.com/hashicorp/packer-plugin-qemu/builder/qemu/step_port_forward.go new file mode 100644 index 000000000..5282cfdf9 --- /dev/null +++ b/vendor/github.com/hashicorp/packer-plugin-qemu/builder/qemu/step_port_forward.go @@ -0,0 +1,74 @@ +package qemu + +import ( + "context" + "fmt" + "log" + + "github.com/hashicorp/packer-plugin-sdk/multistep" + "github.com/hashicorp/packer-plugin-sdk/net" + packersdk "github.com/hashicorp/packer-plugin-sdk/packer" +) + +// This step adds a NAT port forwarding definition so that SSH or WinRM is available +// on the guest machine. +type stepPortForward struct { + CommunicatorType string + NetBridge string + + l *net.Listener +} + +func (s *stepPortForward) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction { + config := state.Get("config").(*Config) + ui := state.Get("ui").(packersdk.Ui) + + if s.CommunicatorType == "none" { + ui.Message("No communicator is set; skipping port forwarding setup.") + return multistep.ActionContinue + } + if s.NetBridge != "" { + ui.Message("net_bridge is set; skipping port forwarding setup.") + return multistep.ActionContinue + } + + commHostPort := config.CommConfig.Comm.Port() + + if config.CommConfig.SkipNatMapping { + log.Printf("Skipping NAT port forwarding. Using communicator (SSH, WinRM, etc) port %d", commHostPort) + state.Put("commHostPort", commHostPort) + return multistep.ActionContinue + } + + log.Printf("Looking for available communicator (SSH, WinRM, etc) port between %d and %d", config.CommConfig.HostPortMin, config.CommConfig.HostPortMax) + var err error + s.l, err = net.ListenRangeConfig{ + Addr: config.VNCBindAddress, + Min: config.CommConfig.HostPortMin, + Max: config.CommConfig.HostPortMax, + Network: "tcp", + }.Listen(ctx) + if err != nil { + err := fmt.Errorf("Error finding port: %s", err) + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } + s.l.Listener.Close() // free port, but don't unlock lock file + commHostPort = s.l.Port + ui.Say(fmt.Sprintf("Found port for communicator (SSH, WinRM, etc): %d.", commHostPort)) + + // Save the port we're using so that future steps can use it + state.Put("commHostPort", commHostPort) + + return multistep.ActionContinue +} + +func (s *stepPortForward) Cleanup(state multistep.StateBag) { + if s.l != nil { + err := s.l.Close() + if err != nil { + log.Printf("failed to unlock port lockfile: %v", err) + } + } +} diff --git a/vendor/github.com/hashicorp/packer-plugin-qemu/builder/qemu/step_prepare_output_dir.go b/vendor/github.com/hashicorp/packer-plugin-qemu/builder/qemu/step_prepare_output_dir.go new file mode 100644 index 000000000..26e9b69cf --- /dev/null +++ b/vendor/github.com/hashicorp/packer-plugin-qemu/builder/qemu/step_prepare_output_dir.go @@ -0,0 +1,51 @@ +package qemu + +import ( + "context" + "log" + "os" + "time" + + "github.com/hashicorp/packer-plugin-sdk/multistep" + packersdk "github.com/hashicorp/packer-plugin-sdk/packer" +) + +type stepPrepareOutputDir struct{} + +func (stepPrepareOutputDir) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction { + config := state.Get("config").(*Config) + ui := state.Get("ui").(packersdk.Ui) + + if _, err := os.Stat(config.OutputDir); err == nil && config.PackerForce { + ui.Say("Deleting previous output directory...") + os.RemoveAll(config.OutputDir) + } + + if err := os.MkdirAll(config.OutputDir, 0755); err != nil { + state.Put("error", err) + return multistep.ActionHalt + } + + return multistep.ActionContinue +} + +func (stepPrepareOutputDir) Cleanup(state multistep.StateBag) { + _, cancelled := state.GetOk(multistep.StateCancelled) + _, halted := state.GetOk(multistep.StateHalted) + + if cancelled || halted { + config := state.Get("config").(*Config) + ui := state.Get("ui").(packersdk.Ui) + + ui.Say("Deleting output directory...") + for i := 0; i < 5; i++ { + err := os.RemoveAll(config.OutputDir) + if err == nil { + break + } + + log.Printf("Error removing output dir: %s", err) + time.Sleep(2 * time.Second) + } + } +} diff --git a/vendor/github.com/hashicorp/packer-plugin-qemu/builder/qemu/step_resize_disk.go b/vendor/github.com/hashicorp/packer-plugin-qemu/builder/qemu/step_resize_disk.go new file mode 100644 index 000000000..65b9592af --- /dev/null +++ b/vendor/github.com/hashicorp/packer-plugin-qemu/builder/qemu/step_resize_disk.go @@ -0,0 +1,60 @@ +package qemu + +import ( + "context" + "fmt" + "path/filepath" + + "github.com/hashicorp/packer-plugin-sdk/multistep" + packersdk "github.com/hashicorp/packer-plugin-sdk/packer" +) + +// This step resizes the virtual disk that will be used as the +// hard drive for the virtual machine. +type stepResizeDisk struct { + DiskCompression bool + DiskImage bool + Format string + OutputDir string + SkipResizeDisk bool + VMName string + DiskSize string + + QemuImgArgs QemuImgArgs +} + +func (s *stepResizeDisk) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction { + driver := state.Get("driver").(Driver) + ui := state.Get("ui").(packersdk.Ui) + path := filepath.Join(s.OutputDir, s.VMName) + + command := s.buildResizeCommand(path) + + if s.DiskImage == false || s.SkipResizeDisk == true { + return multistep.ActionContinue + } + + ui.Say("Resizing hard drive...") + if err := driver.QemuImg(command...); err != nil { + err := fmt.Errorf("Error creating hard drive: %s", err) + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } + + return multistep.ActionContinue +} + +func (s *stepResizeDisk) buildResizeCommand(path string) []string { + command := []string{"resize", "-f", s.Format} + + // add user-provided convert args + command = append(command, s.QemuImgArgs.Resize...) + + // Add file and size + command = append(command, path, s.DiskSize) + + return command +} + +func (s *stepResizeDisk) Cleanup(state multistep.StateBag) {} diff --git a/vendor/github.com/hashicorp/packer-plugin-qemu/builder/qemu/step_run.go b/vendor/github.com/hashicorp/packer-plugin-qemu/builder/qemu/step_run.go new file mode 100644 index 000000000..37239396f --- /dev/null +++ b/vendor/github.com/hashicorp/packer-plugin-qemu/builder/qemu/step_run.go @@ -0,0 +1,400 @@ +package qemu + +import ( + "context" + "fmt" + "log" + "path/filepath" + "strings" + + "github.com/hashicorp/go-version" + "github.com/hashicorp/packer-plugin-sdk/multistep" + packersdk "github.com/hashicorp/packer-plugin-sdk/packer" + "github.com/hashicorp/packer-plugin-sdk/template/interpolate" +) + +// stepRun runs the virtual machine +type stepRun struct { + DiskImage bool + + atLeastVersion2 bool + ui packersdk.Ui +} + +func (s *stepRun) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction { + config := state.Get("config").(*Config) + driver := state.Get("driver").(Driver) + s.ui = state.Get("ui").(packersdk.Ui) + + // Figure out version of qemu; store on step for later use + rawVersion, err := driver.Version() + if err != nil { + err := fmt.Errorf("Error determining qemu version: %s", err) + s.ui.Error(err.Error()) + return multistep.ActionHalt + } + qemuVersion, err := version.NewVersion(rawVersion) + if err != nil { + err := fmt.Errorf("Error parsing qemu version: %s", err) + s.ui.Error(err.Error()) + return multistep.ActionHalt + } + v2 := version.Must(version.NewVersion("2.0")) + + s.atLeastVersion2 = qemuVersion.GreaterThanOrEqual(v2) + + // Generate the qemu command + command, err := s.getCommandArgs(config, state) + if err != nil { + err := fmt.Errorf("Error processing QemuArgs: %s", err) + s.ui.Error(err.Error()) + return multistep.ActionHalt + } + + // run the qemu command + if err := driver.Qemu(command...); err != nil { + err := fmt.Errorf("Error launching VM: %s", err) + s.ui.Error(err.Error()) + return multistep.ActionHalt + } + + return multistep.ActionContinue +} + +func (s *stepRun) Cleanup(state multistep.StateBag) { + driver := state.Get("driver").(Driver) + ui := state.Get("ui").(packersdk.Ui) + + if err := driver.Stop(); err != nil { + ui.Error(fmt.Sprintf("Error shutting down VM: %s", err)) + } +} + +func (s *stepRun) getDefaultArgs(config *Config, state multistep.StateBag) map[string]interface{} { + + defaultArgs := make(map[string]interface{}) + + // Configure "boot" arguement + // Run command is different depending whether we're booting from an + // installation CD or a pre-baked image + bootDrive := "once=d" + message := "Starting VM, booting from CD-ROM" + if s.DiskImage { + bootDrive = "c" + message = "Starting VM, booting disk image" + } + s.ui.Say(message) + defaultArgs["-boot"] = bootDrive + + // configure "-qmp" arguments + if config.QMPEnable { + defaultArgs["-qmp"] = fmt.Sprintf("unix:%s,server,nowait", config.QMPSocketPath) + } + + // configure "-name" arguments + defaultArgs["-name"] = config.VMName + + // Configure "-machine" arguments + if config.Accelerator == "none" { + defaultArgs["-machine"] = fmt.Sprintf("type=%s", config.MachineType) + s.ui.Message("WARNING: The VM will be started with no hardware acceleration.\n" + + "The installation may take considerably longer to finish.\n") + } else { + defaultArgs["-machine"] = fmt.Sprintf("type=%s,accel=%s", + config.MachineType, config.Accelerator) + } + + // Firmware + if config.Firmware != "" { + defaultArgs["-bios"] = config.Firmware + } + + // Configure "-netdev" arguments + defaultArgs["-netdev"] = fmt.Sprintf("bridge,id=user.0,br=%s", config.NetBridge) + if config.NetBridge == "" { + defaultArgs["-netdev"] = fmt.Sprintf("user,id=user.0") + if config.CommConfig.Comm.Type != "none" { + commHostPort := state.Get("commHostPort").(int) + defaultArgs["-netdev"] = fmt.Sprintf("user,id=user.0,hostfwd=tcp::%v-:%d", commHostPort, config.CommConfig.Comm.Port()) + } + } + + // Configure "-vnc" arguments + // vncPort is always set in stepConfigureVNC, so we don't need to + // defensively assert + vncPort := state.Get("vnc_port").(int) + vncIP := config.VNCBindAddress + + vncRealAddress := fmt.Sprintf("%s:%d", vncIP, vncPort) + vncPort = vncPort - 5900 + vncArgs := fmt.Sprintf("%s:%d", vncIP, vncPort) + if config.VNCUsePassword { + vncArgs = fmt.Sprintf("%s:%d,password", vncIP, vncPort) + } + defaultArgs["-vnc"] = vncArgs + + // Track the connection for the user + vncPass, _ := state.Get("vnc_password").(string) + + message = getVncConnectionMessage(config.Headless, vncRealAddress, vncPass) + if message != "" { + s.ui.Message(message) + } + + // Configure "-m" memory argument + defaultArgs["-m"] = fmt.Sprintf("%dM", config.MemorySize) + + // Configure "-smp" processor hardware arguments + if config.CpuCount > 1 { + defaultArgs["-smp"] = fmt.Sprintf("cpus=%d,sockets=%d", config.CpuCount, config.CpuCount) + } + + // Configure "-fda" floppy disk attachment + if floppyPathRaw, ok := state.GetOk("floppy_path"); ok { + defaultArgs["-fda"] = floppyPathRaw.(string) + } else { + log.Println("Qemu Builder has no floppy files, not attaching a floppy.") + } + + // Configure GUI display + if !config.Headless { + if s.atLeastVersion2 { + // FIXME: "none" is a valid display option in qemu but we have + // departed from the qemu usage here to instaed mean "let qemu + // set a reasonable default". We need to deprecate this behavior + // and let users just set "UseDefaultDisplay" if they want to let + // qemu do its thing. + if len(config.Display) > 0 && config.Display != "none" { + defaultArgs["-display"] = config.Display + } else if !config.UseDefaultDisplay { + defaultArgs["-display"] = "gtk" + } + } else { + s.ui.Message("WARNING: The version of qemu on your host doesn't support display mode.\n" + + "The display parameter will be ignored.") + } + } + + deviceArgs, driveArgs := s.getDeviceAndDriveArgs(config, state) + defaultArgs["-device"] = deviceArgs + defaultArgs["-drive"] = driveArgs + + return defaultArgs +} + +func getVncConnectionMessage(headless bool, vnc string, vncPass string) string { + // Configure GUI display + if headless { + if vnc == "" { + return "The VM will be run headless, without a GUI, as configured.\n" + + "If the run isn't succeeding as you expect, please enable the GUI\n" + + "to inspect the progress of the build." + } + + if vncPass != "" { + return fmt.Sprintf( + "The VM will be run headless, without a GUI. If you want to\n"+ + "view the screen of the VM, connect via VNC to vnc://%s\n"+ + "with the password: %s", vnc, vncPass) + } + + return fmt.Sprintf( + "The VM will be run headless, without a GUI. If you want to\n"+ + "view the screen of the VM, connect via VNC without a password to\n"+ + "vnc://%s", vnc) + } + return "" +} + +func (s *stepRun) getDeviceAndDriveArgs(config *Config, state multistep.StateBag) ([]string, []string) { + var deviceArgs []string + var driveArgs []string + + vmName := config.VMName + imgPath := filepath.Join(config.OutputDir, vmName) + + // Configure virtual hard drives + if s.atLeastVersion2 { + drivesToAttach := []string{} + + if v, ok := state.GetOk("qemu_disk_paths"); ok { + diskFullPaths := v.([]string) + drivesToAttach = append(drivesToAttach, diskFullPaths...) + } + + for i, drivePath := range drivesToAttach { + driveArgumentString := fmt.Sprintf("file=%s,if=%s,cache=%s,discard=%s,format=%s", drivePath, config.DiskInterface, config.DiskCache, config.DiskDiscard, config.Format) + if config.DiskInterface == "virtio-scsi" { + // TODO: Megan: Remove this conditional. This, and the code + // under the TODO below, reproduce the old behavior. While it + // may be broken, the goal of this commit is to refactor in a way + // that creates a result that is testably the same as the old + // code. A pr will follow fixing this broken behavior. + if i == 0 { + deviceArgs = append(deviceArgs, fmt.Sprintf("virtio-scsi-pci,id=scsi%d", i)) + } + // TODO: Megan: When you remove above conditional, + // set deviceArgs = append(deviceArgs, fmt.Sprintf("scsi-hd,bus=scsi%d.0,drive=drive%d", i, i)) + deviceArgs = append(deviceArgs, fmt.Sprintf("scsi-hd,bus=scsi0.0,drive=drive%d", i)) + driveArgumentString = fmt.Sprintf("if=none,file=%s,id=drive%d,cache=%s,discard=%s,format=%s", drivePath, i, config.DiskCache, config.DiskDiscard, config.Format) + } + if config.DetectZeroes != "off" { + driveArgumentString = fmt.Sprintf("%s,detect-zeroes=%s", driveArgumentString, config.DetectZeroes) + } + driveArgs = append(driveArgs, driveArgumentString) + } + } else { + driveArgs = append(driveArgs, fmt.Sprintf("file=%s,if=%s,cache=%s,format=%s", imgPath, config.DiskInterface, config.DiskCache, config.Format)) + } + + deviceArgs = append(deviceArgs, fmt.Sprintf("%s,netdev=user.0", config.NetDevice)) + + // Configure virtual CDs + cdPaths := []string{} + // Add the installation CD to the run command + if !config.DiskImage { + isoPath := state.Get("iso_path").(string) + cdPaths = append(cdPaths, isoPath) + } + // Add our custom CD created from cd_files, if it exists + cdFilesPath, ok := state.Get("cd_path").(string) + if ok { + if cdFilesPath != "" { + cdPaths = append(cdPaths, cdFilesPath) + } + } + for i, cdPath := range cdPaths { + if config.CDROMInterface == "" { + driveArgs = append(driveArgs, fmt.Sprintf("file=%s,media=cdrom", cdPath)) + } else if config.CDROMInterface == "virtio-scsi" { + driveArgs = append(driveArgs, fmt.Sprintf("file=%s,if=none,index=%d,id=cdrom%d,media=cdrom", cdPath, i, i)) + deviceArgs = append(deviceArgs, "virtio-scsi-device", fmt.Sprintf("scsi-cd,drive=cdrom%d", i)) + } else { + driveArgs = append(driveArgs, fmt.Sprintf("file=%s,if=%s,index=%d,id=cdrom%d,media=cdrom", cdPath, config.CDROMInterface, i, i)) + } + } + + return deviceArgs, driveArgs +} + +func (s *stepRun) applyUserOverrides(defaultArgs map[string]interface{}, config *Config, state multistep.StateBag) ([]string, error) { + // Done setting up defaults; time to process user args and defaults together + // and generate output args + + inArgs := make(map[string][]string) + if len(config.QemuArgs) > 0 { + s.ui.Say("Overriding default Qemu arguments with qemuargs template option...") + + commHostPort := 0 + if config.CommConfig.Comm.Type != "none" { + if v, ok := state.GetOk("commHostPort"); ok { + commHostPort = v.(int) + } + } + httpIp := state.Get("http_ip").(string) + httpPort := state.Get("http_port").(int) + + type qemuArgsTemplateData struct { + HTTPIP string + HTTPPort int + HTTPDir string + HTTPContent map[string]string + OutputDir string + Name string + SSHHostPort int + } + + ictx := config.ctx + ictx.Data = qemuArgsTemplateData{ + HTTPIP: httpIp, + HTTPPort: httpPort, + HTTPDir: config.HTTPDir, + HTTPContent: config.HTTPContent, + OutputDir: config.OutputDir, + Name: config.VMName, + SSHHostPort: commHostPort, + } + + // Interpolate each string in qemuargs + newQemuArgs, err := processArgs(config.QemuArgs, &ictx) + if err != nil { + return nil, err + } + + // Qemu supports multiple appearances of the same switch. This means + // each key in the args hash will have an array of string values + for _, qemuArgs := range newQemuArgs { + key := qemuArgs[0] + val := strings.Join(qemuArgs[1:], "") + if _, ok := inArgs[key]; !ok { + inArgs[key] = make([]string, 0) + } + if len(val) > 0 { + inArgs[key] = append(inArgs[key], val) + } + } + } + + // get any remaining missing default args from the default settings + for key := range defaultArgs { + if _, ok := inArgs[key]; !ok { + arg := make([]string, 1) + switch defaultArgs[key].(type) { + case string: + arg[0] = defaultArgs[key].(string) + case []string: + arg = defaultArgs[key].([]string) + } + inArgs[key] = arg + } + } + + // Check if we are missing the netDevice #6804 + if x, ok := inArgs["-device"]; ok { + if !strings.Contains(strings.Join(x, ""), config.NetDevice) { + inArgs["-device"] = append(inArgs["-device"], fmt.Sprintf("%s,netdev=user.0", config.NetDevice)) + } + } + + // Flatten to array of strings + outArgs := make([]string, 0) + for key, values := range inArgs { + if len(values) > 0 { + for idx := range values { + outArgs = append(outArgs, key, values[idx]) + } + } else { + outArgs = append(outArgs, key) + } + } + + return outArgs, nil +} + +func (s *stepRun) getCommandArgs(config *Config, state multistep.StateBag) ([]string, error) { + defaultArgs := s.getDefaultArgs(config, state) + + return s.applyUserOverrides(defaultArgs, config, state) +} + +func processArgs(args [][]string, ctx *interpolate.Context) ([][]string, error) { + var err error + + if args == nil { + return make([][]string, 0), err + } + + newArgs := make([][]string, len(args)) + for argsIdx, rowArgs := range args { + parms := make([]string, len(rowArgs)) + newArgs[argsIdx] = parms + for i, parm := range rowArgs { + parms[i], err = interpolate.Render(parm, ctx) + if err != nil { + return nil, err + } + } + } + + return newArgs, err +} diff --git a/vendor/github.com/hashicorp/packer-plugin-qemu/builder/qemu/step_set_iso.go b/vendor/github.com/hashicorp/packer-plugin-qemu/builder/qemu/step_set_iso.go new file mode 100644 index 000000000..cc4054d46 --- /dev/null +++ b/vendor/github.com/hashicorp/packer-plugin-qemu/builder/qemu/step_set_iso.go @@ -0,0 +1,54 @@ +package qemu + +import ( + "context" + "fmt" + "net/http" + + "github.com/hashicorp/packer-plugin-sdk/multistep" + "github.com/hashicorp/packer-plugin-sdk/net" + packersdk "github.com/hashicorp/packer-plugin-sdk/packer" +) + +// This step set iso_patch to available url +type stepSetISO struct { + ResultKey string + Url []string +} + +func (s *stepSetISO) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction { + ui := state.Get("ui").(packersdk.Ui) + + iso_path := "" + + for _, url := range s.Url { + req, err := http.NewRequest("HEAD", url, nil) + if err != nil { + continue + } + + req.Header.Set("User-Agent", "Packer") + + httpClient := net.HttpClientWithEnvironmentProxy() + + res, err := httpClient.Do(req) + if err == nil && (res.StatusCode >= 200 && res.StatusCode < 300) { + if res.Header.Get("Accept-Ranges") == "bytes" { + iso_path = url + } + } + } + + if iso_path == "" { + err := fmt.Errorf("No byte serving support. The HTTP server must support Accept-Ranges=bytes") + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } + + state.Put(s.ResultKey, iso_path) + + return multistep.ActionContinue +} + +func (s *stepSetISO) Cleanup(state multistep.StateBag) {} diff --git a/vendor/github.com/hashicorp/packer-plugin-qemu/builder/qemu/step_shutdown.go b/vendor/github.com/hashicorp/packer-plugin-qemu/builder/qemu/step_shutdown.go new file mode 100644 index 000000000..b7cd17777 --- /dev/null +++ b/vendor/github.com/hashicorp/packer-plugin-qemu/builder/qemu/step_shutdown.go @@ -0,0 +1,94 @@ +package qemu + +import ( + "context" + "errors" + "fmt" + "log" + "time" + + "github.com/hashicorp/packer-plugin-sdk/communicator" + "github.com/hashicorp/packer-plugin-sdk/multistep" + packersdk "github.com/hashicorp/packer-plugin-sdk/packer" +) + +// This step shuts down the machine. It first attempts to do so gracefully, +// but ultimately forcefully shuts it down if that fails. +// +// Uses: +// communicator packersdk.Communicator +// config *config +// driver Driver +// ui packersdk.Ui +// +// Produces: +// +type stepShutdown struct { + ShutdownCommand string + ShutdownTimeout time.Duration + Comm *communicator.Config +} + +func (s *stepShutdown) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction { + driver := state.Get("driver").(Driver) + ui := state.Get("ui").(packersdk.Ui) + + if s.Comm.Type == "none" { + cancelCh := make(chan struct{}, 1) + go func() { + defer close(cancelCh) + <-time.After(s.ShutdownTimeout) + }() + ui.Say("Waiting for shutdown...") + if ok := driver.WaitForShutdown(cancelCh); ok { + log.Println("VM shut down.") + return multistep.ActionContinue + } else { + err := fmt.Errorf("Failed to shutdown") + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } + } + + if s.ShutdownCommand != "" { + comm := state.Get("communicator").(packersdk.Communicator) + ui.Say("Gracefully halting virtual machine...") + log.Printf("Executing shutdown command: %s", s.ShutdownCommand) + cmd := &packersdk.RemoteCmd{Command: s.ShutdownCommand} + if err := cmd.RunWithUi(ctx, comm, ui); err != nil { + err := fmt.Errorf("Failed to send shutdown command: %s", err) + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } + + // Start the goroutine that will time out our graceful attempt + cancelCh := make(chan struct{}, 1) + go func() { + defer close(cancelCh) + <-time.After(s.ShutdownTimeout) + }() + + log.Printf("Waiting max %s for shutdown to complete", s.ShutdownTimeout) + if ok := driver.WaitForShutdown(cancelCh); !ok { + err := errors.New("Timeout while waiting for machine to shut down.") + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } + } else { + ui.Say("Halting the virtual machine...") + if err := driver.Stop(); err != nil { + err := fmt.Errorf("Error stopping VM: %s", err) + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } + } + + log.Println("VM shut down.") + return multistep.ActionContinue +} + +func (s *stepShutdown) Cleanup(state multistep.StateBag) {} diff --git a/vendor/github.com/hashicorp/packer-plugin-qemu/builder/qemu/step_type_boot_command.go b/vendor/github.com/hashicorp/packer-plugin-qemu/builder/qemu/step_type_boot_command.go new file mode 100644 index 000000000..038f18d62 --- /dev/null +++ b/vendor/github.com/hashicorp/packer-plugin-qemu/builder/qemu/step_type_boot_command.go @@ -0,0 +1,139 @@ +package qemu + +import ( + "context" + "fmt" + "log" + "net" + "time" + + "github.com/hashicorp/packer-plugin-sdk/bootcommand" + "github.com/hashicorp/packer-plugin-sdk/multistep" + packersdk "github.com/hashicorp/packer-plugin-sdk/packer" + "github.com/hashicorp/packer-plugin-sdk/template/interpolate" + "github.com/mitchellh/go-vnc" +) + +const KeyLeftShift uint32 = 0xFFE1 + +type bootCommandTemplateData struct { + HTTPIP string + HTTPPort int + Name string +} + +// This step "types" the boot command into the VM over VNC. +// +// Uses: +// config *config +// http_port int +// ui packersdk.Ui +// vnc_port int +// +// Produces: +// +type stepTypeBootCommand struct{} + +func (s *stepTypeBootCommand) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction { + config := state.Get("config").(*Config) + debug := state.Get("debug").(bool) + httpPort := state.Get("http_port").(int) + ui := state.Get("ui").(packersdk.Ui) + vncPort := state.Get("vnc_port").(int) + vncIP := config.VNCBindAddress + vncPassword := state.Get("vnc_password") + + if config.VNCConfig.DisableVNC { + log.Println("Skipping boot command step...") + return multistep.ActionContinue + } + + // Wait the for the vm to boot. + if int64(config.BootWait) > 0 { + ui.Say(fmt.Sprintf("Waiting %s for boot...", config.BootWait)) + select { + case <-time.After(config.BootWait): + break + case <-ctx.Done(): + return multistep.ActionHalt + } + } + + var pauseFn multistep.DebugPauseFn + if debug { + pauseFn = state.Get("pauseFn").(multistep.DebugPauseFn) + } + + // Connect to VNC + ui.Say(fmt.Sprintf("Connecting to VM via VNC (%s:%d)", vncIP, vncPort)) + + nc, err := net.Dial("tcp", fmt.Sprintf("%s:%d", vncIP, vncPort)) + if err != nil { + err := fmt.Errorf("Error connecting to VNC: %s", err) + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } + defer nc.Close() + + var auth []vnc.ClientAuth + + if vncPassword != nil && len(vncPassword.(string)) > 0 { + auth = []vnc.ClientAuth{&vnc.PasswordAuth{Password: vncPassword.(string)}} + } else { + auth = []vnc.ClientAuth{new(vnc.ClientAuthNone)} + } + + c, err := vnc.Client(nc, &vnc.ClientConfig{Auth: auth, Exclusive: false}) + if err != nil { + err := fmt.Errorf("Error handshaking with VNC: %s", err) + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } + defer c.Close() + + log.Printf("Connected to VNC desktop: %s", c.DesktopName) + + hostIP := state.Get("http_ip").(string) + configCtx := config.ctx + configCtx.Data = &bootCommandTemplateData{ + hostIP, + httpPort, + config.VMName, + } + + d := bootcommand.NewVNCDriver(c, config.VNCConfig.BootKeyInterval) + + ui.Say("Typing the boot command over VNC...") + command, err := interpolate.Render(config.VNCConfig.FlatBootCommand(), &configCtx) + if err != nil { + err := fmt.Errorf("Error preparing boot command: %s", err) + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } + + seq, err := bootcommand.GenerateExpressionSequence(command) + if err != nil { + err := fmt.Errorf("Error generating boot command: %s", err) + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } + + if err := seq.Do(ctx, d); err != nil { + err := fmt.Errorf("Error running boot command: %s", err) + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } + + if pauseFn != nil { + pauseFn(multistep.DebugLocationAfterRun, fmt.Sprintf("boot_command: %s", command), state) + } + + return multistep.ActionContinue +} + +func (*stepTypeBootCommand) Cleanup(multistep.StateBag) {} diff --git a/vendor/github.com/hashicorp/packer-plugin-qemu/builder/qemu/step_wait_guest_address.go b/vendor/github.com/hashicorp/packer-plugin-qemu/builder/qemu/step_wait_guest_address.go new file mode 100644 index 000000000..c15da17f0 --- /dev/null +++ b/vendor/github.com/hashicorp/packer-plugin-qemu/builder/qemu/step_wait_guest_address.go @@ -0,0 +1,136 @@ +package qemu + +import ( + "bufio" + "context" + "fmt" + "log" + "os" + "strconv" + "strings" + "time" + + "github.com/hashicorp/packer-plugin-sdk/multistep" + packersdk "github.com/hashicorp/packer-plugin-sdk/packer" + + "github.com/digitalocean/go-qemu/qmp" +) + +// This step waits for the guest address to become available in the network +// bridge, then it sets the guestAddress state property. +type stepWaitGuestAddress struct { + CommunicatorType string + NetBridge string + + timeout time.Duration +} + +func (s *stepWaitGuestAddress) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction { + ui := state.Get("ui").(packersdk.Ui) + + if s.CommunicatorType == "none" { + ui.Message("No communicator is configured -- skipping StepWaitGuestAddress") + return multistep.ActionContinue + } + if s.NetBridge == "" { + ui.Message("Not using a NetBridge -- skipping StepWaitGuestAddress") + return multistep.ActionContinue + } + + qmpMonitor := state.Get("qmp_monitor").(*qmp.SocketMonitor) + ctx, cancel := context.WithTimeout(ctx, s.timeout) + defer cancel() + + ui.Say(fmt.Sprintf("Waiting for the guest address to become available in the %s network bridge...", s.NetBridge)) + for { + guestAddress := getGuestAddress(qmpMonitor, s.NetBridge, "user.0") + if guestAddress != "" { + log.Printf("Found guest address %s", guestAddress) + state.Put("guestAddress", guestAddress) + return multistep.ActionContinue + } + select { + case <-time.After(10 * time.Second): + continue + case <-ctx.Done(): + return multistep.ActionHalt + } + } +} + +func (s *stepWaitGuestAddress) Cleanup(state multistep.StateBag) { +} + +func getGuestAddress(qmpMonitor *qmp.SocketMonitor, bridgeName string, deviceName string) string { + devices, err := getNetDevices(qmpMonitor) + if err != nil { + log.Printf("Could not retrieve QEMU QMP network device list: %v", err) + return "" + } + + for _, device := range devices { + if device.Name == deviceName { + ipAddress, _ := getDeviceIPAddress(bridgeName, device.MacAddress) + return ipAddress + } + } + + log.Printf("QEMU QMP network device %s was not found", deviceName) + return "" +} + +func getDeviceIPAddress(device string, macAddress string) (string, error) { + // this parses /proc/net/arp to retrieve the given device IP address. + // + // /proc/net/arp is normally someting alike: + // + // IP address HW type Flags HW address Mask Device + // 192.168.121.111 0x1 0x2 52:54:00:12:34:56 * virbr0 + // + + const ( + IPAddressIndex int = iota + HWTypeIndex + FlagsIndex + HWAddressIndex + MaskIndex + DeviceIndex + ) + + // see ARP flags at https://github.com/torvalds/linux/blob/v5.4/include/uapi/linux/if_arp.h#L132 + const ( + AtfCom int = 0x02 // ATF_COM (complete) + ) + + f, err := os.Open("/proc/net/arp") + if err != nil { + return "", fmt.Errorf("failed to open /proc/net/arp: %w", err) + } + defer f.Close() + + s := bufio.NewScanner(f) + s.Scan() + + for s.Scan() { + fields := strings.Fields(s.Text()) + + if device != "" && fields[DeviceIndex] != device { + continue + } + + if fields[HWAddressIndex] != macAddress { + continue + } + + flags, err := strconv.ParseInt(fields[FlagsIndex], 0, 32) + if err != nil { + return "", fmt.Errorf("failed to parse /proc/net/arp flags field %s: %w", fields[FlagsIndex], err) + } + + if int(flags)&AtfCom == AtfCom { + return fields[IPAddressIndex], nil + } + } + + return "", fmt.Errorf("could not find %s", macAddress) +} diff --git a/vendor/modules.txt b/vendor/modules.txt index 0a2a74cba..d4f82df49 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -281,8 +281,7 @@ github.com/digitalocean/go-libvirt github.com/digitalocean/go-libvirt/internal/constants github.com/digitalocean/go-libvirt/internal/event github.com/digitalocean/go-libvirt/internal/go-xdr/xdr2 -# github.com/digitalocean/go-qemu v0.0.0-20201211181942-d361e7b4965f -## explicit +# github.com/digitalocean/go-qemu v0.0.0-20210326154740-ac9e0b687001 github.com/digitalocean/go-qemu/qmp # github.com/digitalocean/godo v1.11.1 ## explicit @@ -524,6 +523,9 @@ github.com/hashicorp/packer-plugin-googlecompute/builder/googlecompute github.com/hashicorp/packer-plugin-googlecompute/builder/googlecompute/version github.com/hashicorp/packer-plugin-googlecompute/post-processor/googlecompute-export github.com/hashicorp/packer-plugin-googlecompute/post-processor/googlecompute-import +# github.com/hashicorp/packer-plugin-qemu v0.0.0-20210419131938-9ec808d0c364 +## explicit +github.com/hashicorp/packer-plugin-qemu/builder/qemu # github.com/hashicorp/packer-plugin-sdk v0.2.0 ## explicit github.com/hashicorp/packer-plugin-sdk/acctest @@ -681,7 +683,6 @@ github.com/mitchellh/go-homedir # github.com/mitchellh/go-testing-interface v1.0.3 github.com/mitchellh/go-testing-interface # github.com/mitchellh/go-vnc v0.0.0-20150629162542-723ed9867aed -## explicit github.com/mitchellh/go-vnc # github.com/mitchellh/go-wordwrap v1.0.0 github.com/mitchellh/go-wordwrap