commit 6dba5713382dda9761f13e70d975160031691906 Author: Cécile MORANGE Date: Tue Feb 14 15:48:24 2023 +0100 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3de3eea --- /dev/null +++ b/.gitignore @@ -0,0 +1,11 @@ +variables.pkrvars.hcl +*.swp +*~ +bin +pkg +crash.log +packer_cache/* +builder-* +dist/* +vendor/* +packer-plugin-xenserver \ No newline at end of file diff --git a/.goreleaser.yml b/.goreleaser.yml new file mode 100644 index 0000000..71544a4 --- /dev/null +++ b/.goreleaser.yml @@ -0,0 +1,74 @@ +# This is an example goreleaser.yaml file with some sane defaults. +# Make sure to check the documentation at http://goreleaser.com +env: + - CGO_ENABLED=0 +before: + hooks: + # TODO(ddelnano): for now there aren't tests to run + # - go test ./... + # As part of the release doc files are included as a separate deliverable for + # consumption by Packer.io. To include a separate docs.zip uncomment the following command. + #- make ci-release-docs + # Check plugin compatibility with required version of the Packer SDK + - make plugin-check +builds: + # A separated build to run the packer-plugins-check only once for a linux_amd64 binary + - + id: plugin-check + mod_timestamp: '{{ .CommitTimestamp }}' + flags: + - -trimpath #removes all file system paths from the compiled executable + ldflags: + - '-s -w -X {{ .ModulePath }}/version.Version={{.Version}} -X {{ .ModulePath }}/version.VersionPrerelease= ' + goos: + - linux + goarch: + - amd64 + binary: '{{ .ProjectName }}_v{{ .Version }}_{{ .Env.API_VERSION }}_{{ .Os }}_{{ .Arch }}' + - + mod_timestamp: '{{ .CommitTimestamp }}' + flags: + - -trimpath #removes all file system paths from the compiled executable + ldflags: + - '-s -w -X {{ .ModulePath }}/version.Version={{.Version}} -X {{ .ModulePath }}/version.VersionPrerelease= ' + goos: + - freebsd + - windows + - linux + - darwin + goarch: + - amd64 + - '386' + - arm + - arm64 + ignore: + - goos: darwin + goarch: '386' + - goos: linux + goarch: amd64 + binary: '{{ .ProjectName }}_v{{ .Version }}_{{ .Env.API_VERSION }}_{{ .Os }}_{{ .Arch }}' +archives: +- format: zip + files: + - none* + name_template: '{{ .ProjectName }}_v{{ .Version }}_{{ .Env.API_VERSION }}_{{ .Os }}_{{ .Arch }}' +checksum: + name_template: '{{ .ProjectName }}_v{{ .Version }}_SHA256SUMS' + algorithm: sha256 +signs: + - artifacts: checksum + args: + # if you are using this is in a GitHub action or some other automated pipeline, you + # need to pass the batch flag to indicate its not interactive. + - "--batch" + - "--local-user" + - "{{ .Env.GPG_FINGERPRINT }}" + - "--output" + - "${signature}" + - "--detach-sign" + - "${artifact}" +release: + draft: true + +changelog: + skip: true diff --git a/GNUmakefile b/GNUmakefile new file mode 100644 index 0000000..98fba69 --- /dev/null +++ b/GNUmakefile @@ -0,0 +1,34 @@ +NAME=xenserver +BINARY=packer-plugin-${NAME} + +COUNT?=1 +TEST?=$(shell go list ./...) +HASHICORP_PACKER_PLUGIN_SDK_VERSION?=$(shell go list -m github.com/hashicorp/packer-plugin-sdk | cut -d " " -f2) + +.PHONY: dev + +build: + @go build -o ${BINARY} + +dev: build + @mkdir -p ~/.packer.d/plugins/ + @mv ${BINARY} ~/.packer.d/plugins/${BINARY} + +test: + @go test -race -count $(COUNT) $(TEST) -timeout=3m + +install-packer-sdc: ## Install packer sofware development command + @go install github.com/hashicorp/packer-plugin-sdk/cmd/packer-sdc@${HASHICORP_PACKER_PLUGIN_SDK_VERSION} + +ci-release-docs: install-packer-sdc + @packer-sdc renderdocs -src docs -partials docs-partials/ -dst docs/ + @/bin/sh -c "[ -d docs ] && zip -r docs.zip docs/" + +plugin-check: install-packer-sdc build + @packer-sdc plugin-check ${BINARY} + +testacc: dev + @PACKER_ACC=1 go test -count $(COUNT) -v $(TEST) -timeout=120m + +generate: install-packer-sdc + @go generate ./... diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..a612ad9 --- /dev/null +++ b/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/README.md b/README.md new file mode 100644 index 0000000..bd6511d --- /dev/null +++ b/README.md @@ -0,0 +1,73 @@ +# XenServer packer.io builder + +This builder plugin extends packer.io to support building images for XenServer. + +This is a fork of the original builder since the original project was abandoned and no longer compilied with recent versions of Go or worked with Xenserver 7.6 and later. + +It improves the original project in the following ways: +1. Developed alongside the [Xenorchestra terraform provider](https://github.com/ddelnano/terraform-provider-xenorchestra) to ensure the hashicorp ecosystem is interoperable. +2. Reimplements how the boot commands are sent over VNC to be compatible with later versions of Xenserver (Citrix hypervisor) and XCP + +## Status + +At the time of this writing the packer builder has been verified to work with Xenserver 7.6 and can launch VMs with the packer output through the xenorchestra terraform provider. + +The following list contains things that are incomplete but will be worked on soon: +- The documentation is still in an inconsistent state with upstream +- XVA builder is untested +- Lots of dead code to remove from upstream + +## Using the builder + +The packer builder can be installed via `packer init` as long as the packer template includes the following in it's `pkr.hcl` file +``` +packer { + required_plugins { + xenserver= { + version = ">= v0.3.2" + source = "github.com/ddelnano/xenserver" + } + } +} +``` + +The following command will install the packer plugin using the Ubuntu example provided in this repository. + +``` +packer init examples/ubuntu/ubuntu-2004.pkr.hcl +``` + +If you are using an older version of packer or are still using json templates you will need to download the relevant release from the project's [releases page](https://github.com/ddelnano/packer-builder-xenserver/releases) and copy the binary to `~/.packer.d/plugins/packer-builder-xenserver-iso`. + +## Developing the builder + +### Dependencies +* Packer >= v1.7.1 (https://packer.io) +* XenServer / Citrix Hypervisor > 7.6 +* Golang 1.16 + +## Compile the plugin + +Once you have installed Packer, you must compile this plugin and install the +resulting binary. + +```shell +$ go build -o packer-plugin-xenserver + +# Add the builder to the location packer expects it to be installed in +$ mkdir -p ~/.packer.d/plugins/ +$ cp builder-xenserver-iso ~/.packer.d/plugins/packer-builder-xenserver-iso +``` + +# Documentation + +For complete documentation on configuration commands, see [the +xenserver-iso docs](docs/builders/xenserver-iso.html.markdown) + +## Support + +You can discuss any issues you have or feature requests in [Discord](https://discord.gg/ZpNq8ez). + +If you'd like to support my effort on the project, please consider buying me a coffee + +[!["Buy Me A Coffee"](https://www.buymeacoffee.com/assets/img/custom_images/orange_img.png)](https://www.buymeacoffee.com/ddelnano) diff --git a/builder/xenserver/common/artifact.go b/builder/xenserver/common/artifact.go new file mode 100644 index 0000000..32b6531 --- /dev/null +++ b/builder/xenserver/common/artifact.go @@ -0,0 +1,58 @@ +package common + +import ( + "fmt" + "github.com/hashicorp/packer-plugin-sdk/packer" + "os" + "path/filepath" +) + +// This is the common builder ID to all of these artifacts. +const BuilderId = "packer.xenserver" + +type LocalArtifact struct { + dir string + f []string +} + +func NewArtifact(dir string) (packer.Artifact, error) { + files := make([]string, 0, 1) + visit := func(path string, info os.FileInfo, err error) error { + if !info.IsDir() { + files = append(files, path) + } + return err + } + if err := filepath.Walk(dir, visit); err != nil { + return nil, err + } + + return &LocalArtifact{ + dir: dir, + f: files, + }, nil +} + +func (*LocalArtifact) BuilderId() string { + return BuilderId +} + +func (a *LocalArtifact) Files() []string { + return a.f +} + +func (*LocalArtifact) Id() string { + return "VM" +} + +func (a *LocalArtifact) String() string { + return fmt.Sprintf("VM files in directory: %s", a.dir) +} + +func (a *LocalArtifact) State(name string) interface{} { + return nil +} + +func (a *LocalArtifact) Destroy() error { + return os.RemoveAll(a.dir) +} diff --git a/builder/xenserver/common/client.go b/builder/xenserver/common/client.go new file mode 100644 index 0000000..6341986 --- /dev/null +++ b/builder/xenserver/common/client.go @@ -0,0 +1,974 @@ +package common + +import ( + "encoding/xml" + "errors" + "fmt" + "log" + + xmlrpc "github.com/amfranz/go-xmlrpc-client" + xenapi "github.com/terra-farm/go-xen-api-client" +) + +type XenAPIClient struct { + Session interface{} + Host string + Url string + Username string + Password string + RPC *xmlrpc.Client +} + +type APIResult struct { + Status string + Value interface{} + ErrorDescription string +} + +type XenAPIObject struct { + Ref string + Client *XenAPIClient +} + +type Host XenAPIObject +type VM XenAPIObject +type SR XenAPIObject +type VDI XenAPIObject +type Network XenAPIObject +type VBD XenAPIObject +type VIF XenAPIObject +type PIF XenAPIObject +type Pool XenAPIObject +type Task XenAPIObject + +type VDIType int + +const ( + _ VDIType = iota + Disk + CD + Floppy +) + +type TaskStatusType int + +const ( + _ TaskStatusType = iota + Pending + Success + Failure + Cancelling + Cancelled +) + +func (c *XenAPIClient) RPCCall(result interface{}, method string, params []interface{}) (err error) { + fmt.Println(params) + p := xmlrpc.Params{Params: params} + err = c.RPC.Call(method, p, result) + return err +} + +func (client *XenAPIClient) Login() (err error) { + //Do loging call + result := xmlrpc.Struct{} + + params := make([]interface{}, 2) + params[0] = client.Username + params[1] = client.Password + + err = client.RPCCall(&result, "session.login_with_password", params) + client.Session = result["Value"] + return err +} + +func (client *XenAPIClient) APICall(result *APIResult, method string, params ...interface{}) (err error) { + if client.Session == nil { + fmt.Println("Error: no session") + return fmt.Errorf("No session. Unable to make call") + } + + //Make a params slice which will include the session + p := make([]interface{}, len(params)+1) + p[0] = client.Session + + if params != nil { + for idx, element := range params { + p[idx+1] = element + } + } + + res := xmlrpc.Struct{} + + err = client.RPCCall(&res, method, p) + + if err != nil { + return err + } + + result.Status = res["Status"].(string) + + if result.Status != "Success" { + fmt.Println("Encountered an API error: ", result.Status) + fmt.Println(res["ErrorDescription"]) + return fmt.Errorf("API Error: %s", res["ErrorDescription"]) + } else { + result.Value = res["Value"] + } + return +} + +func (client *XenAPIClient) GetHosts() (hosts []*Host, err error) { + hosts = make([]*Host, 0) + result := APIResult{} + _ = client.APICall(&result, "host.get_all") + for _, elem := range result.Value.([]interface{}) { + host := new(Host) + host.Ref = elem.(string) + host.Client = client + hosts = append(hosts, host) + } + return +} + +func (client *XenAPIClient) GetPools() (pools []*Pool, err error) { + pools = make([]*Pool, 0) + result := APIResult{} + err = client.APICall(&result, "pool.get_all") + if err != nil { + return pools, err + } + + for _, elem := range result.Value.([]interface{}) { + pool := new(Pool) + pool.Ref = elem.(string) + pool.Client = client + pools = append(pools, pool) + } + + return pools, nil +} + +func (client *XenAPIClient) GetDefaultSR() (sr *SR, err error) { + pools, err := client.GetPools() + + if err != nil { + return nil, err + } + + pool_rec, err := pools[0].GetRecord() + + if err != nil { + return nil, err + } + + if pool_rec["default_SR"] == "" { + return nil, errors.New("No default_SR specified for the pool.") + } + + sr = new(SR) + sr.Ref = pool_rec["default_SR"].(string) + sr.Client = client + + return sr, nil +} + +func (client *XenAPIClient) GetVMByUuid(vm_uuid string) (vm *VM, err error) { + vm = new(VM) + result := APIResult{} + err = client.APICall(&result, "VM.get_by_uuid", vm_uuid) + if err != nil { + return nil, err + } + vm.Ref = result.Value.(string) + vm.Client = client + return +} + +func (client *XenAPIClient) GetVMByNameLabel(name_label string) (vms []*VM, err error) { + vms = make([]*VM, 0) + result := APIResult{} + err = client.APICall(&result, "VM.get_by_name_label", name_label) + if err != nil { + return vms, err + } + + for _, elem := range result.Value.([]interface{}) { + vm := new(VM) + vm.Ref = elem.(string) + vm.Client = client + vms = append(vms, vm) + } + + return vms, nil +} + +func (client *XenAPIClient) GetNetworkByUuid(network_uuid string) (network *Network, err error) { + network = new(Network) + result := APIResult{} + err = client.APICall(&result, "network.get_by_uuid", network_uuid) + if err != nil { + return nil, err + } + network.Ref = result.Value.(string) + network.Client = client + return +} + +func (client *XenAPIClient) GetNetworkByNameLabel(name_label string) (networks []*Network, err error) { + networks = make([]*Network, 0) + result := APIResult{} + err = client.APICall(&result, "network.get_by_name_label", name_label) + if err != nil { + return networks, err + } + + for _, elem := range result.Value.([]interface{}) { + network := new(Network) + network.Ref = elem.(string) + network.Client = client + networks = append(networks, network) + } + + return networks, nil +} + +func (client *XenAPIClient) GetVdiByNameLabel(name_label string) (vdis []*VDI, err error) { + vdis = make([]*VDI, 0) + result := APIResult{} + err = client.APICall(&result, "VDI.get_by_name_label", name_label) + if err != nil { + return vdis, err + } + + for _, elem := range result.Value.([]interface{}) { + vdi := new(VDI) + vdi.Ref = elem.(string) + vdi.Client = client + vdis = append(vdis, vdi) + } + + return vdis, nil +} + +func (client *XenAPIClient) GetVdiByUuid(vdi_uuid string) (vdi *VDI, err error) { + vdi = new(VDI) + result := APIResult{} + err = client.APICall(&result, "VDI.get_by_uuid", vdi_uuid) + if err != nil { + return nil, err + } + vdi.Ref = result.Value.(string) + vdi.Client = client + return +} + +func (client *XenAPIClient) GetPIFs() (pifs []*PIF, err error) { + pifs = make([]*PIF, 0) + result := APIResult{} + err = client.APICall(&result, "PIF.get_all") + if err != nil { + return pifs, err + } + for _, elem := range result.Value.([]interface{}) { + pif := new(PIF) + pif.Ref = elem.(string) + pif.Client = client + pifs = append(pifs, pif) + } + + return pifs, nil +} + +// Host associated functions + +func (self *Host) GetSoftwareVersion() (versions map[string]interface{}, err error) { + versions = make(map[string]interface{}) + + result := APIResult{} + err = self.Client.APICall(&result, "host.get_software_version", self.Ref) + if err != nil { + return nil, err + } + + for k, v := range result.Value.(xmlrpc.Struct) { + versions[k] = v.(string) + } + return +} + +func (self *Host) CallPlugin(plugin, function string, args map[string]string) (res string, err error) { + + args_rec := make(xmlrpc.Struct) + for key, value := range args { + args_rec[key] = value + } + + result := APIResult{} + err = self.Client.APICall(&result, "host.call_plugin", self.Ref, plugin, function, args_rec) + if err != nil { + return "", err + } + + // The plugin should return a string value + res = result.Value.(string) + return +} + +// VM associated functions + +func (self *VM) Clone(label string) (new_instance *VM, err error) { + new_instance = new(VM) + + result := APIResult{} + err = self.Client.APICall(&result, "VM.clone", self.Ref, label) + if err != nil { + return nil, err + } + new_instance.Ref = result.Value.(string) + new_instance.Client = self.Client + return +} + +func (self *VM) Destroy() (err error) { + result := APIResult{} + err = self.Client.APICall(&result, "VM.destroy", self.Ref) + if err != nil { + return err + } + return +} + +func (self *VM) Start(paused, force bool) (err error) { + result := APIResult{} + err = self.Client.APICall(&result, "VM.start", self.Ref, paused, force) + if err != nil { + return err + } + return +} + +func (self *VM) CleanShutdown() (err error) { + result := APIResult{} + err = self.Client.APICall(&result, "VM.clean_shutdown", self.Ref) + if err != nil { + return err + } + return +} + +func Unpause(c *Connection, vmRef xenapi.VMRef) (err error) { + err = c.client.VM.Unpause(c.session, vmRef) + if err != nil { + return err + } + return +} + +func (self *VM) SetHVMBoot(policy, bootOrder string) (err error) { + result := APIResult{} + err = self.Client.APICall(&result, "VM.set_HVM_boot_policy", self.Ref, policy) + if err != nil { + return err + } + result = APIResult{} + params := make(xmlrpc.Struct) + params["order"] = bootOrder + err = self.Client.APICall(&result, "VM.set_HVM_boot_params", self.Ref, params) + if err != nil { + return err + } + return +} + +func (self *VM) SetPVBootloader(pv_bootloader, pv_args string) (err error) { + result := APIResult{} + err = self.Client.APICall(&result, "VM.set_PV_bootloader", self.Ref, pv_bootloader) + if err != nil { + return err + } + result = APIResult{} + err = self.Client.APICall(&result, "VM.set_PV_bootloader_args", self.Ref, pv_args) + if err != nil { + return err + } + return +} + +func (self *VM) GetDomainId() (domid string, err error) { + result := APIResult{} + err = self.Client.APICall(&result, "VM.get_domid", self.Ref) + if err != nil { + return "", err + } + domid = result.Value.(string) + return domid, nil +} + +func (self *VM) GetPowerState() (state string, err error) { + result := APIResult{} + err = self.Client.APICall(&result, "VM.get_power_state", self.Ref) + if err != nil { + return "", err + } + state = result.Value.(string) + return state, nil +} + +func (self *VM) GetUuid() (uuid string, err error) { + result := APIResult{} + err = self.Client.APICall(&result, "VM.get_uuid", self.Ref) + if err != nil { + return "", err + } + uuid = result.Value.(string) + return uuid, nil +} + +func (self *VM) GetVBDs() (vbds []VBD, err error) { + vbds = make([]VBD, 0) + result := APIResult{} + err = self.Client.APICall(&result, "VM.get_VBDs", self.Ref) + if err != nil { + return vbds, err + } + for _, elem := range result.Value.([]interface{}) { + vbd := VBD{} + vbd.Ref = elem.(string) + vbd.Client = self.Client + vbds = append(vbds, vbd) + } + + return vbds, nil +} + +func GetDisks(c *Connection, vmRef xenapi.VMRef) (vdis []xenapi.VDIRef, err error) { + // Return just data disks (non-isos) + vdis = make([]xenapi.VDIRef, 0) + vbds, err := c.client.VM.GetVBDs(c.session, vmRef) + if err != nil { + return nil, err + } + + for _, vbd := range vbds { + rec, err := c.client.VBD.GetRecord(c.session, vbd) + if err != nil { + return nil, err + } + if rec.Type == "Disk" { + + vdi, err := c.client.VBD.GetVDI(c.session, vbd) + if err != nil { + return nil, err + } + vdis = append(vdis, vdi) + + } + } + return vdis, nil +} + +func (self *VM) GetGuestMetricsRef() (ref string, err error) { + result := APIResult{} + err = self.Client.APICall(&result, "VM.get_guest_metrics", self.Ref) + if err != nil { + return "", nil + } + ref = result.Value.(string) + return ref, err +} + +func (self *VM) GetGuestMetrics() (metrics map[string]interface{}, err error) { + metrics_ref, err := self.GetGuestMetricsRef() + if err != nil { + return nil, err + } + if metrics_ref == "OpaqueRef:NULL" { + return nil, nil + } + + result := APIResult{} + err = self.Client.APICall(&result, "VM_guest_metrics.get_record", metrics_ref) + if err != nil { + return nil, err + } + return result.Value.(xmlrpc.Struct), nil +} + +func (self *VM) SetStaticMemoryRange(min, max uint) (err error) { + result := APIResult{} + strMin := fmt.Sprintf("%d", min) + strMax := fmt.Sprintf("%d", max) + err = self.Client.APICall(&result, "VM.set_memory_limits", self.Ref, strMin, strMax, strMin, strMax) + if err != nil { + return err + } + return +} + +func ConnectVdi(c *Connection, vmRef xenapi.VMRef, vdiRef xenapi.VDIRef, vbdType xenapi.VbdType) (err error) { + + var mode xenapi.VbdMode + var unpluggable bool + var bootable bool + var t xenapi.VbdType + switch vbdType { + case xenapi.VbdTypeCD: + mode = xenapi.VbdModeRO + bootable = true + unpluggable = false + t = xenapi.VbdTypeCD + case xenapi.VbdTypeDisk: + mode = xenapi.VbdModeRW + bootable = false + unpluggable = false + t = xenapi.VbdTypeDisk + case xenapi.VbdTypeFloppy: + mode = xenapi.VbdModeRW + bootable = false + unpluggable = true + t = xenapi.VbdTypeFloppy + } + + vbd_ref, err := c.client.VBD.Create(c.session, xenapi.VBDRecord{ + VM: xenapi.VMRef(vmRef), + VDI: xenapi.VDIRef(vdiRef), + Userdevice: "autodetect", + Empty: false, + // OtherConfig: map[string]interface{{}}, + QosAlgorithmType: "", + // QosAlgorithmParams: map[string]interface{{}}, + Mode: mode, + Unpluggable: unpluggable, + Bootable: bootable, + Type: t, + }) + + if err != nil { + return err + } + + fmt.Println("VBD Ref:", vbd_ref) + + uuid, err := c.client.VBD.GetUUID(c.session, vbd_ref) + + fmt.Println("VBD UUID: ", uuid) + /* + // 2. Plug VBD (Non need - the VM hasn't booted. + // @todo - check VM state + result = APIResult{} + err = self.Client.APICall(&result, "VBD.plug", vbd_ref) + + if err != nil { + return err + } + */ + return +} + +func DisconnectVdi(c *Connection, vmRef xenapi.VMRef, vdi xenapi.VDIRef) error { + vbds, err := c.client.VM.GetVBDs(c.session, vmRef) + if err != nil { + return fmt.Errorf("Unable to get VM VBDs: %s", err.Error()) + } + + for _, vbd := range vbds { + rec, err := c.client.VBD.GetRecord(c.session, vbd) + if err != nil { + return fmt.Errorf("Could not get record for VBD '%s': %s", vbd, err.Error()) + } + recVdi := rec.VDI + if recVdi == vdi { + _ = c.client.VBD.Unplug(c.session, vbd) + err = c.client.VBD.Destroy(c.session, vbd) + if err != nil { + return fmt.Errorf("Could not destroy VBD '%s': %s", vbd, err.Error()) + } + + return nil + } else { + log.Printf("Could not find VDI record in VBD '%s'", vbd) + } + } + + return fmt.Errorf("Could not find VBD for VDI '%s'", vdi) +} + +func (self *VM) SetPlatform(params map[string]string) (err error) { + result := APIResult{} + platform_rec := make(xmlrpc.Struct) + for key, value := range params { + platform_rec[key] = value + } + + err = self.Client.APICall(&result, "VM.set_platform", self.Ref, platform_rec) + + if err != nil { + return err + } + return +} + +func ConnectNetwork(c *Connection, networkRef xenapi.NetworkRef, vmRef xenapi.VMRef, device string) (*xenapi.VIFRef, error) { + vif, err := c.client.VIF.Create(c.session, xenapi.VIFRecord{ + Network: networkRef, + VM: vmRef, + Device: device, + LockingMode: xenapi.VifLockingModeNetworkDefault, + }) + + if err != nil { + return nil, err + } + log.Printf("Created the following VIF: %s", vif) + + return &vif, nil +} + +// Setters + +func (self *VM) SetIsATemplate(is_a_template bool) (err error) { + result := APIResult{} + err = self.Client.APICall(&result, "VM.set_is_a_template", self.Ref, is_a_template) + if err != nil { + return err + } + return +} + +// SR associated functions + +func (self *SR) CreateVdi(name_label string, size int64) (vdi *VDI, err error) { + vdi = new(VDI) + + vdi_rec := make(xmlrpc.Struct) + vdi_rec["name_label"] = name_label + vdi_rec["SR"] = self.Ref + vdi_rec["virtual_size"] = fmt.Sprintf("%d", size) + vdi_rec["type"] = "user" + vdi_rec["sharable"] = false + vdi_rec["read_only"] = false + + oc := make(xmlrpc.Struct) + oc["temp"] = "temp" + vdi_rec["other_config"] = oc + + result := APIResult{} + err = self.Client.APICall(&result, "VDI.create", vdi_rec) + if err != nil { + return nil, err + } + + vdi.Ref = result.Value.(string) + vdi.Client = self.Client + + return +} + +// Network associated functions + +func (self *Network) GetAssignedIPs() (ip_map map[string]string, err error) { + ip_map = make(map[string]string, 0) + result := APIResult{} + err = self.Client.APICall(&result, "network.get_assigned_ips", self.Ref) + if err != nil { + return ip_map, err + } + for k, v := range result.Value.(xmlrpc.Struct) { + ip_map[k] = v.(string) + } + return ip_map, nil +} + +// PIF associated functions + +func (self *PIF) GetRecord() (record map[string]interface{}, err error) { + record = make(map[string]interface{}) + result := APIResult{} + err = self.Client.APICall(&result, "PIF.get_record", self.Ref) + if err != nil { + return record, err + } + for k, v := range result.Value.(xmlrpc.Struct) { + record[k] = v + } + return record, nil +} + +// Pool associated functions + +func (self *Pool) GetRecord() (record map[string]interface{}, err error) { + record = make(map[string]interface{}) + result := APIResult{} + err = self.Client.APICall(&result, "pool.get_record", self.Ref) + if err != nil { + return record, err + } + for k, v := range result.Value.(xmlrpc.Struct) { + record[k] = v + } + return record, nil +} + +// VBD associated functions +func (self *VBD) GetRecord() (record map[string]interface{}, err error) { + record = make(map[string]interface{}) + result := APIResult{} + err = self.Client.APICall(&result, "VBD.get_record", self.Ref) + if err != nil { + return record, err + } + for k, v := range result.Value.(xmlrpc.Struct) { + record[k] = v + } + return record, nil +} + +func (self *VBD) GetVDI() (vdi *VDI, err error) { + vbd_rec, err := self.GetRecord() + if err != nil { + return nil, err + } + + vdi = new(VDI) + vdi.Ref = vbd_rec["VDI"].(string) + vdi.Client = self.Client + + return vdi, nil +} + +func (self *VBD) Eject() (err error) { + result := APIResult{} + err = self.Client.APICall(&result, "VBD.eject", self.Ref) + if err != nil { + return err + } + return nil +} + +func (self *VBD) Unplug() (err error) { + result := APIResult{} + err = self.Client.APICall(&result, "VBD.unplug", self.Ref) + if err != nil { + return err + } + return nil +} + +func (self *VBD) Destroy() (err error) { + result := APIResult{} + err = self.Client.APICall(&result, "VBD.destroy", self.Ref) + if err != nil { + return err + } + return nil +} + +// VIF associated functions + +func (self *VIF) Destroy() (err error) { + result := APIResult{} + err = self.Client.APICall(&result, "VIF.destroy", self.Ref) + if err != nil { + return err + } + return nil +} + +// VDI associated functions + +func (self *VDI) GetUuid() (vdi_uuid string, err error) { + result := APIResult{} + err = self.Client.APICall(&result, "VDI.get_uuid", self.Ref) + if err != nil { + return "", err + } + vdi_uuid = result.Value.(string) + return vdi_uuid, nil +} + +func (self *VDI) GetVBDs() (vbds []VBD, err error) { + vbds = make([]VBD, 0) + result := APIResult{} + err = self.Client.APICall(&result, "VDI.get_VBDs", self.Ref) + if err != nil { + return vbds, err + } + for _, elem := range result.Value.([]interface{}) { + vbd := VBD{} + vbd.Ref = elem.(string) + vbd.Client = self.Client + vbds = append(vbds, vbd) + } + + return vbds, nil +} + +func (self *VDI) Destroy() (err error) { + result := APIResult{} + err = self.Client.APICall(&result, "VDI.destroy", self.Ref) + if err != nil { + return err + } + return +} + +// Expose a VDI using the Transfer VM +// (Legacy VHD export) + +type TransferRecord struct { + UrlFull string `xml:"url_full,attr"` +} + +func Expose(c *Connection, vdiRef xenapi.VDIRef, format string) (url string, err error) { + + hosts, err := c.client.Host.GetAll(c.session) + + if err != nil { + err = errors.New(fmt.Sprintf("Could not retrieve hosts in the pool: %s", err.Error())) + return "", err + } + host := hosts[0] + + if err != nil { + err = errors.New(fmt.Sprintf("Failed to get VDI uuid for %s: %s", vdiRef, err.Error())) + return "", err + } + + args := make(map[string]string) + args["transfer_mode"] = "http" + args["vdi_uuid"] = string(vdiRef) + args["expose_vhd"] = "true" + args["network_uuid"] = "management" + args["timeout_minutes"] = "5" + + handle, err := c.client.Host.CallPlugin(c.session, host, "transfer", "expose", args) + + if err != nil { + err = errors.New(fmt.Sprintf("Error whilst exposing VDI %s: %s", vdiRef, err.Error())) + return "", err + } + + args = make(map[string]string) + args["record_handle"] = handle + record_xml, err := c.client.Host.CallPlugin(c.session, host, "transfer", "get_record", args) + + if err != nil { + err = errors.New(fmt.Sprintf("Unable to retrieve transfer record for VDI %s: %s", vdiRef, err.Error())) + return "", err + } + + var record TransferRecord + xml.Unmarshal([]byte(record_xml), &record) + + if record.UrlFull == "" { + return "", errors.New(fmt.Sprintf("Error: did not parse XML properly: '%s'", record_xml)) + } + + // Handles either raw or VHD formats + + switch format { + case "vhd": + url = fmt.Sprintf("%s.vhd", record.UrlFull) + + case "raw": + url = record.UrlFull + } + + return +} + +func Unexpose(c *Connection, vdiRef xenapi.VDIRef) (err error) { + + disk_uuid, err := c.client.VDI.GetUUID(c.session, vdiRef) + + if err != nil { + return err + } + + hosts, err := c.client.Host.GetAll(c.session) + + if err != nil { + err = errors.New(fmt.Sprintf("Could not retrieve hosts in the pool: %s", err.Error())) + return err + } + + host := hosts[0] + + args := make(map[string]string) + args["vdi_uuid"] = disk_uuid + + result, err := c.client.Host.CallPlugin(c.session, host, "transfer", "unexpose", args) + + if err != nil { + return err + } + + log.Println(fmt.Sprintf("Unexpose result: %s", result)) + + return nil +} + +// Task associated functions + +// func (self *Task) GetResult() (object *XenAPIObject, err error) { +// result := APIResult{} +// err = self.Client.APICall(&result, "task.get_result", self.Ref) +// if err != nil { +// return +// } +// switch ref := result.Value.(type) { +// case string: +// // @fixme: xapi currently sends us an xmlrpc-encoded string via xmlrpc. +// // This seems to be a bug in xapi. Remove this workaround when it's fixed +// re := regexp.MustCompile("^([^<]*).*$") +// match := re.FindStringSubmatch(ref) +// if match == nil { +// object = nil +// } else { +// object = &XenAPIObject{ +// Ref: match[1], +// Client: self.Client, +// } +// } +// case nil: +// object = nil +// default: +// err = fmt.Errorf("task.get_result: unknown value type %T (expected string or nil)", ref) +// } +// return +// } + +// Client Initiator +type Connection struct { + client *xenapi.Client + session xenapi.SessionRef + Host string + Username string + Password string +} + +func (c Connection) GetSession() string { + return string(c.session) +} + +func NewXenAPIClient(host, username, password string) (*Connection, error) { + client, err := xenapi.NewClient("https://"+host, nil) + if err != nil { + return nil, err + } + + session, err := client.Session.LoginWithPassword(username, password, "1.0", "packer") + if err != nil { + return nil, err + } + + return &Connection{client, session, host, username, password}, nil +} + +func (c *Connection) GetClient() *xenapi.Client { + return c.client +} + +func (c *Connection) GetSessionRef() xenapi.SessionRef { + return c.session +} diff --git a/builder/xenserver/common/common_config.go b/builder/xenserver/common/common_config.go new file mode 100644 index 0000000..6907e18 --- /dev/null +++ b/builder/xenserver/common/common_config.go @@ -0,0 +1,289 @@ +package common + +import ( + "errors" + "fmt" + "os" + "time" + + "github.com/hashicorp/packer-plugin-sdk/common" + "github.com/hashicorp/packer-plugin-sdk/multistep" + "github.com/hashicorp/packer-plugin-sdk/template/interpolate" + xenapi "github.com/terra-farm/go-xen-api-client" +) + +type CommonConfig struct { + Username string `mapstructure:"remote_username"` + Password string `mapstructure:"remote_password"` + HostIp string `mapstructure:"remote_host"` + + VMName string `mapstructure:"vm_name"` + VMDescription string `mapstructure:"vm_description"` + SrName string `mapstructure:"sr_name"` + SrISOName string `mapstructure:"sr_iso_name"` + FloppyFiles []string `mapstructure:"floppy_files"` + NetworkNames []string `mapstructure:"network_names"` + ExportNetworkNames []string `mapstructure:"export_network_names"` + + HostPortMin uint `mapstructure:"host_port_min"` + HostPortMax uint `mapstructure:"host_port_max"` + + BootCommand []string `mapstructure:"boot_command"` + ShutdownCommand string `mapstructure:"shutdown_command"` + + RawBootWait string `mapstructure:"boot_wait"` + BootWait time.Duration + + ToolsIsoName string `mapstructure:"tools_iso_name"` + + HTTPDir string `mapstructure:"http_directory"` + HTTPPortMin uint `mapstructure:"http_port_min"` + HTTPPortMax uint `mapstructure:"http_port_max"` + + // SSHHostPortMin uint `mapstructure:"ssh_host_port_min"` + // SSHHostPortMax uint `mapstructure:"ssh_host_port_max"` + SSHKeyPath string `mapstructure:"ssh_key_path"` + SSHPassword string `mapstructure:"ssh_password"` + SSHPort uint `mapstructure:"ssh_port"` + SSHUser string `mapstructure:"ssh_username"` + SSHConfig `mapstructure:",squash"` + + RawSSHWaitTimeout string `mapstructure:"ssh_wait_timeout"` + SSHWaitTimeout time.Duration + + OutputDir string `mapstructure:"output_directory"` + Format string `mapstructure:"format"` + KeepVM string `mapstructure:"keep_vm"` + IPGetter string `mapstructure:"ip_getter"` +} + +func (c *CommonConfig) Prepare(ctx *interpolate.Context, pc *common.PackerConfig) []error { + var err error + var errs []error + + // Set default values + + if c.HostPortMin == 0 { + c.HostPortMin = 5900 + } + + if c.HostPortMax == 0 { + c.HostPortMax = 6000 + } + + if c.RawBootWait == "" { + c.RawBootWait = "5s" + } + + if c.ToolsIsoName == "" { + c.ToolsIsoName = "xs-tools.iso" + } + + if c.HTTPPortMin == 0 { + c.HTTPPortMin = 8000 + } + + if c.HTTPPortMax == 0 { + c.HTTPPortMax = 9000 + } + + if c.RawSSHWaitTimeout == "" { + c.RawSSHWaitTimeout = "200m" + } + + if c.FloppyFiles == nil { + c.FloppyFiles = make([]string, 0) + } + + /* + if c.SSHHostPortMin == 0 { + c.SSHHostPortMin = 2222 + } + + if c.SSHHostPortMax == 0 { + c.SSHHostPortMax = 4444 + } + */ + + if c.SSHPort == 0 { + c.SSHPort = 22 + } + + if c.RawSSHWaitTimeout == "" { + c.RawSSHWaitTimeout = "20m" + } + + if c.OutputDir == "" { + c.OutputDir = fmt.Sprintf("output-%s", pc.PackerBuildName) + } + + if c.VMName == "" { + c.VMName = fmt.Sprintf("packer-%s-{{timestamp}}", pc.PackerBuildName) + } + + if c.Format == "" { + c.Format = "xva" + } + + if c.KeepVM == "" { + c.KeepVM = "never" + } + + if c.IPGetter == "" { + c.IPGetter = "auto" + } + + // Validation + + if c.Username == "" { + errs = append(errs, errors.New("remote_username must be specified.")) + } + + if c.Password == "" { + errs = append(errs, errors.New("remote_password must be specified.")) + } + + if c.HostIp == "" { + errs = append(errs, errors.New("remote_host must be specified.")) + } + + if c.HostPortMin > c.HostPortMax { + errs = append(errs, errors.New("the host min port must be less than the max")) + } + + if c.HTTPPortMin > c.HTTPPortMax { + errs = append(errs, errors.New("the HTTP min port must be less than the max")) + } + + c.BootWait, err = time.ParseDuration(c.RawBootWait) + if err != nil { + errs = append(errs, fmt.Errorf("Failed to parse boot_wait: %s", err)) + } + + if c.SSHKeyPath != "" { + if _, err := os.Stat(c.SSHKeyPath); err != nil { + errs = append(errs, fmt.Errorf("ssh_key_path is invalid: %s", err)) + } else if _, err := FileSigner(c.SSHKeyPath); err != nil { + errs = append(errs, fmt.Errorf("ssh_key_path is invalid: %s", err)) + } + } + + /* + if c.SSHHostPortMin > c.SSHHostPortMax { + errs = append(errs, + errors.New("ssh_host_port_min must be less than ssh_host_port_max")) + } + */ + + if c.SSHUser == "" { + errs = append(errs, errors.New("An ssh_username must be specified.")) + } + + c.SSHWaitTimeout, err = time.ParseDuration(c.RawSSHWaitTimeout) + if err != nil { + errs = append(errs, fmt.Errorf("Failed to parse ssh_wait_timeout: %s", err)) + } + + switch c.Format { + case "xva", "xva_compressed", "vdi_raw", "vdi_vhd", "none": + default: + errs = append(errs, errors.New("format must be one of 'xva', 'vdi_raw', 'vdi_vhd', 'none'")) + } + + switch c.KeepVM { + case "always", "never", "on_success": + default: + errs = append(errs, errors.New("keep_vm must be one of 'always', 'never', 'on_success'")) + } + + switch c.IPGetter { + case "auto", "tools", "http": + default: + errs = append(errs, errors.New("ip_getter must be one of 'auto', 'tools', 'http'")) + } + + return errs +} + +// steps should check config.ShouldKeepVM first before cleaning up the VM +func (c CommonConfig) ShouldKeepVM(state multistep.StateBag) bool { + switch c.KeepVM { + case "always": + return true + case "never": + return false + case "on_success": + // only keep instance if build was successful + _, cancelled := state.GetOk(multistep.StateCancelled) + _, halted := state.GetOk(multistep.StateHalted) + return !(cancelled || halted) + default: + panic(fmt.Sprintf("Unknown keep_vm value '%s'", c.KeepVM)) + } +} + +func (config CommonConfig) GetSR(c *Connection) (xenapi.SRRef, error) { + var srRef xenapi.SRRef + if config.SrName == "" { + hostRef, err := c.GetClient().Session.GetThisHost(c.session, c.session) + + if err != nil { + return srRef, err + } + + pools, err := c.GetClient().Pool.GetAllRecords(c.session) + + if err != nil { + return srRef, err + } + + for _, pool := range pools { + if pool.Master == hostRef { + return pool.DefaultSR, nil + } + } + + return srRef, errors.New(fmt.Sprintf("failed to find default SR on host '%s'", hostRef)) + + } else { + // Use the provided name label to find the SR to use + srs, err := c.GetClient().SR.GetByNameLabel(c.session, config.SrName) + + if err != nil { + return srRef, err + } + + switch { + case len(srs) == 0: + return srRef, fmt.Errorf("Couldn't find a SR with the specified name-label '%s'", config.SrName) + case len(srs) > 1: + return srRef, fmt.Errorf("Found more than one SR with the name '%s'. The name must be unique", config.SrName) + } + + return srs[0], nil + } +} + +func (config CommonConfig) GetISOSR(c *Connection) (xenapi.SRRef, error) { + var srRef xenapi.SRRef + if config.SrISOName == "" { + return srRef, errors.New("sr_iso_name must be specified in the packer configuration") + + } else { + // Use the provided name label to find the SR to use + srs, err := c.GetClient().SR.GetByNameLabel(c.session, config.SrName) + + if err != nil { + return srRef, err + } + + switch { + case len(srs) == 0: + return srRef, fmt.Errorf("Couldn't find a SR with the specified name-label '%s'", config.SrName) + case len(srs) > 1: + return srRef, fmt.Errorf("Found more than one SR with the name '%s'. The name must be unique", config.SrName) + } + + return srs[0], nil + } +} diff --git a/builder/xenserver/common/config.go b/builder/xenserver/common/config.go new file mode 100644 index 0000000..53e76b7 --- /dev/null +++ b/builder/xenserver/common/config.go @@ -0,0 +1,43 @@ +//go:generate mapstructure-to-hcl2 -type Config +package common + +import ( + "time" + + "github.com/hashicorp/packer-plugin-sdk/common" + "github.com/hashicorp/packer-plugin-sdk/communicator" + "github.com/hashicorp/packer-plugin-sdk/template/interpolate" +) + +type Config struct { + common.PackerConfig `mapstructure:",squash"` + CommonConfig `mapstructure:",squash"` + Comm communicator.Config `mapstructure:",squash"` + + VCPUsMax uint `mapstructure:"vcpus_max"` + VCPUsAtStartup uint `mapstructure:"vcpus_atstartup"` + VMMemory uint `mapstructure:"vm_memory"` + DiskSize uint `mapstructure:"disk_size"` + CloneTemplate string `mapstructure:"clone_template"` + VMOtherConfig map[string]string `mapstructure:"vm_other_config"` + + ISOChecksum string `mapstructure:"iso_checksum"` + ISOChecksumType string `mapstructure:"iso_checksum_type"` + ISOUrls []string `mapstructure:"iso_urls"` + ISOUrl string `mapstructure:"iso_url"` + ISOName string `mapstructure:"iso_name"` + + PlatformArgs map[string]string `mapstructure:"platform_args"` + + RawInstallTimeout string `mapstructure:"install_timeout"` + InstallTimeout time.Duration `` + SourcePath string `mapstructure:"source_path"` + + Firmware string `mapstructure:"firmware"` + + ctx interpolate.Context +} + +func (c Config) GetInterpContext() *interpolate.Context { + return &c.ctx +} diff --git a/builder/xenserver/common/config.hcl2spec.go b/builder/xenserver/common/config.hcl2spec.go new file mode 100644 index 0000000..65644ea --- /dev/null +++ b/builder/xenserver/common/config.hcl2spec.go @@ -0,0 +1,225 @@ +// Code generated by "mapstructure-to-hcl2 -type Config"; DO NOT EDIT. + +package common + +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"` + 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"` + Username *string `mapstructure:"remote_username" cty:"remote_username" hcl:"remote_username"` + Password *string `mapstructure:"remote_password" cty:"remote_password" hcl:"remote_password"` + HostIp *string `mapstructure:"remote_host" cty:"remote_host" hcl:"remote_host"` + VMName *string `mapstructure:"vm_name" cty:"vm_name" hcl:"vm_name"` + VMDescription *string `mapstructure:"vm_description" cty:"vm_description" hcl:"vm_description"` + SrName *string `mapstructure:"sr_name" cty:"sr_name" hcl:"sr_name"` + SrISOName *string `mapstructure:"sr_iso_name" cty:"sr_iso_name" hcl:"sr_iso_name"` + FloppyFiles []string `mapstructure:"floppy_files" cty:"floppy_files" hcl:"floppy_files"` + NetworkNames []string `mapstructure:"network_names" cty:"network_names" hcl:"network_names"` + ExportNetworkNames []string `mapstructure:"export_network_names" cty:"export_network_names" hcl:"export_network_names"` + HostPortMin *uint `mapstructure:"host_port_min" cty:"host_port_min" hcl:"host_port_min"` + HostPortMax *uint `mapstructure:"host_port_max" cty:"host_port_max" hcl:"host_port_max"` + BootCommand []string `mapstructure:"boot_command" cty:"boot_command" hcl:"boot_command"` + ShutdownCommand *string `mapstructure:"shutdown_command" cty:"shutdown_command" hcl:"shutdown_command"` + RawBootWait *string `mapstructure:"boot_wait" cty:"boot_wait" hcl:"boot_wait"` + ToolsIsoName *string `mapstructure:"tools_iso_name" cty:"tools_iso_name" hcl:"tools_iso_name"` + HTTPDir *string `mapstructure:"http_directory" cty:"http_directory" hcl:"http_directory"` + HTTPPortMin *uint `mapstructure:"http_port_min" cty:"http_port_min" hcl:"http_port_min"` + HTTPPortMax *uint `mapstructure:"http_port_max" cty:"http_port_max" hcl:"http_port_max"` + SSHKeyPath *string `mapstructure:"ssh_key_path" cty:"ssh_key_path" hcl:"ssh_key_path"` + SSHPassword *string `mapstructure:"ssh_password" cty:"ssh_password" hcl:"ssh_password"` + SSHPort *uint `mapstructure:"ssh_port" cty:"ssh_port" hcl:"ssh_port"` + SSHUser *string `mapstructure:"ssh_username" cty:"ssh_username" hcl:"ssh_username"` + 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"` + 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"` + SSHHostPortMin *uint `mapstructure:"ssh_host_port_min" cty:"ssh_host_port_min" hcl:"ssh_host_port_min"` + SSHHostPortMax *uint `mapstructure:"ssh_host_port_max" cty:"ssh_host_port_max" hcl:"ssh_host_port_max"` + SSHSkipNatMapping *bool `mapstructure:"ssh_skip_nat_mapping" cty:"ssh_skip_nat_mapping" hcl:"ssh_skip_nat_mapping"` + OutputDir *string `mapstructure:"output_directory" cty:"output_directory" hcl:"output_directory"` + Format *string `mapstructure:"format" cty:"format" hcl:"format"` + KeepVM *string `mapstructure:"keep_vm" cty:"keep_vm" hcl:"keep_vm"` + IPGetter *string `mapstructure:"ip_getter" cty:"ip_getter" hcl:"ip_getter"` + VCPUsMax *uint `mapstructure:"vcpus_max" cty:"vcpus_max" hcl:"vcpus_max"` + VCPUsAtStartup *uint `mapstructure:"vcpus_atstartup" cty:"vcpus_atstartup" hcl:"vcpus_atstartup"` + VMMemory *uint `mapstructure:"vm_memory" cty:"vm_memory" hcl:"vm_memory"` + DiskSize *uint `mapstructure:"disk_size" cty:"disk_size" hcl:"disk_size"` + CloneTemplate *string `mapstructure:"clone_template" cty:"clone_template" hcl:"clone_template"` + VMOtherConfig map[string]string `mapstructure:"vm_other_config" cty:"vm_other_config" hcl:"vm_other_config"` + ISOChecksum *string `mapstructure:"iso_checksum" cty:"iso_checksum" hcl:"iso_checksum"` + ISOChecksumType *string `mapstructure:"iso_checksum_type" cty:"iso_checksum_type" hcl:"iso_checksum_type"` + ISOUrls []string `mapstructure:"iso_urls" cty:"iso_urls" hcl:"iso_urls"` + ISOUrl *string `mapstructure:"iso_url" cty:"iso_url" hcl:"iso_url"` + ISOName *string `mapstructure:"iso_name" cty:"iso_name" hcl:"iso_name"` + PlatformArgs map[string]string `mapstructure:"platform_args" cty:"platform_args" hcl:"platform_args"` + RawInstallTimeout *string `mapstructure:"install_timeout" cty:"install_timeout" hcl:"install_timeout"` + SourcePath *string `mapstructure:"source_path" cty:"source_path" hcl:"source_path"` + Firmware *string `mapstructure:"firmware" cty:"firmware" hcl:"firmware"` +} + +// 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_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}, + "remote_username": &hcldec.AttrSpec{Name: "remote_username", Type: cty.String, Required: false}, + "remote_password": &hcldec.AttrSpec{Name: "remote_password", Type: cty.String, Required: false}, + "remote_host": &hcldec.AttrSpec{Name: "remote_host", Type: cty.String, Required: false}, + "vm_name": &hcldec.AttrSpec{Name: "vm_name", Type: cty.String, Required: false}, + "vm_description": &hcldec.AttrSpec{Name: "vm_description", Type: cty.String, Required: false}, + "sr_name": &hcldec.AttrSpec{Name: "sr_name", Type: cty.String, Required: false}, + "sr_iso_name": &hcldec.AttrSpec{Name: "sr_iso_name", Type: cty.String, Required: false}, + "floppy_files": &hcldec.AttrSpec{Name: "floppy_files", Type: cty.List(cty.String), Required: false}, + "network_names": &hcldec.AttrSpec{Name: "network_names", Type: cty.List(cty.String), Required: false}, + "export_network_names": &hcldec.AttrSpec{Name: "export_network_names", Type: cty.List(cty.String), 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}, + "boot_command": &hcldec.AttrSpec{Name: "boot_command", Type: cty.List(cty.String), Required: false}, + "shutdown_command": &hcldec.AttrSpec{Name: "shutdown_command", Type: cty.String, Required: false}, + "boot_wait": &hcldec.AttrSpec{Name: "boot_wait", Type: cty.String, Required: false}, + "tools_iso_name": &hcldec.AttrSpec{Name: "tools_iso_name", Type: cty.String, Required: false}, + "http_directory": &hcldec.AttrSpec{Name: "http_directory", Type: 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}, + "ssh_key_path": &hcldec.AttrSpec{Name: "ssh_key_path", Type: cty.String, Required: false}, + "ssh_password": &hcldec.AttrSpec{Name: "ssh_password", 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}, + "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_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}, + "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}, + "ssh_skip_nat_mapping": &hcldec.AttrSpec{Name: "ssh_skip_nat_mapping", Type: cty.Bool, Required: false}, + "output_directory": &hcldec.AttrSpec{Name: "output_directory", Type: cty.String, Required: false}, + "format": &hcldec.AttrSpec{Name: "format", Type: cty.String, Required: false}, + "keep_vm": &hcldec.AttrSpec{Name: "keep_vm", Type: cty.String, Required: false}, + "ip_getter": &hcldec.AttrSpec{Name: "ip_getter", Type: cty.String, Required: false}, + "vcpus_max": &hcldec.AttrSpec{Name: "vcpus_max", Type: cty.Number, Required: false}, + "vcpus_atstartup": &hcldec.AttrSpec{Name: "vcpus_atstartup", Type: cty.Number, Required: false}, + "vm_memory": &hcldec.AttrSpec{Name: "vm_memory", Type: cty.Number, Required: false}, + "disk_size": &hcldec.AttrSpec{Name: "disk_size", Type: cty.Number, Required: false}, + "clone_template": &hcldec.AttrSpec{Name: "clone_template", Type: cty.String, Required: false}, + "vm_other_config": &hcldec.AttrSpec{Name: "vm_other_config", Type: cty.Map(cty.String), Required: false}, + "iso_checksum": &hcldec.AttrSpec{Name: "iso_checksum", Type: cty.String, Required: false}, + "iso_checksum_type": &hcldec.AttrSpec{Name: "iso_checksum_type", Type: cty.String, Required: false}, + "iso_urls": &hcldec.AttrSpec{Name: "iso_urls", Type: cty.List(cty.String), Required: false}, + "iso_url": &hcldec.AttrSpec{Name: "iso_url", Type: cty.String, Required: false}, + "iso_name": &hcldec.AttrSpec{Name: "iso_name", Type: cty.String, Required: false}, + "platform_args": &hcldec.AttrSpec{Name: "platform_args", Type: cty.Map(cty.String), Required: false}, + "install_timeout": &hcldec.AttrSpec{Name: "install_timeout", Type: cty.String, Required: false}, + "source_path": &hcldec.AttrSpec{Name: "source_path", Type: cty.String, Required: false}, + "firmware": &hcldec.AttrSpec{Name: "firmware", Type: cty.String, Required: false}, + } + return s +} diff --git a/builder/xenserver/common/find_port.go b/builder/xenserver/common/find_port.go new file mode 100644 index 0000000..ea75a73 --- /dev/null +++ b/builder/xenserver/common/find_port.go @@ -0,0 +1,25 @@ +package common + +import ( + "fmt" + "log" + "net" +) + +// FindPort finds and starts listening on a port in the range [portMin, portMax] +// returns the listener and the port number on success, or nil, 0 on failure +func FindPort(portMin uint, portMax uint) (net.Listener, uint) { + log.Printf("Looking for an available port between %d and %d", portMin, portMax) + + for port := portMin; port <= portMax; port++ { + log.Printf("Trying port: %d", port) + l, err := net.Listen("tcp", fmt.Sprintf(":%d", port)) + if err == nil { + return l, port + } else { + log.Printf("Port %d unavailable: %s", port, err.Error()) + } + } + + return nil, 0 +} diff --git a/builder/xenserver/common/http_upload.go b/builder/xenserver/common/http_upload.go new file mode 100644 index 0000000..156ae7c --- /dev/null +++ b/builder/xenserver/common/http_upload.go @@ -0,0 +1,127 @@ +package common + +import ( + "crypto/tls" + "fmt" + "log" + "net/http" + "net/url" + "os" + "time" + + "github.com/hashicorp/packer-plugin-sdk/multistep" + "github.com/hashicorp/packer-plugin-sdk/packer" + xsclient "github.com/terra-farm/go-xen-api-client" +) + +func appendQuery(urlstring, k, v string) (string, error) { + u, err := url.Parse(urlstring) + if err != nil { + return "", fmt.Errorf("Unable to parse URL '%s': %s", urlstring, err.Error()) + } + m := u.Query() + m.Add(k, v) + u.RawQuery = m.Encode() + return u.String(), err +} + +func HTTPUpload(import_url string, fh *os.File, state multistep.StateBag) (result string, err error) { + ui := state.Get("ui").(packer.Ui) + c := state.Get("client").(*Connection) + + task, err := c.client.Task.Create(c.session, "packer-task", "Packer task") + if err != nil { + err = fmt.Errorf("Unable to create task: %s", err.Error()) + return + } + defer c.client.Task.Destroy(c.session, task) + + import_task_url, err := appendQuery(import_url, "task_id", string(task)) + if err != nil { + return + } + + // Get file length + fstat, err := fh.Stat() + if err != nil { + err = fmt.Errorf("Unable to stat '%s': %s", fh.Name(), err.Error()) + return + } + fileLength := fstat.Size() + + // Define a new transport which allows self-signed certs + tr := &http.Transport{ + TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, + } + + // Create a client + httpClient := &http.Client{Transport: tr} + + // Create request and download file + request, err := http.NewRequest("PUT", import_task_url, fh) + request.ContentLength = fileLength + + ui.Say(fmt.Sprintf("PUT '%s'", import_task_url)) + + resp, err := httpClient.Do(request) // Do closes fh for us, according to docs + if err != nil { + return + } + + if resp.StatusCode != 200 { + err = fmt.Errorf("PUT request got non-200 status code: %s", resp.Status) + return + } + + logIteration := 0 + err = InterruptibleWait{ + Predicate: func() (bool, error) { + status, err := c.client.Task.GetStatus(c.session, task) + if err != nil { + return false, fmt.Errorf("Failed to get task status: %s", err.Error()) + } + switch status { + case xsclient.TaskStatusTypePending: + progress, err := c.client.Task.GetProgress(c.session, task) + if err != nil { + return false, fmt.Errorf("Failed to get progress: %s", err.Error()) + } + logIteration = logIteration + 1 + if logIteration%5 == 0 { + log.Printf("Upload %.0f%% complete", progress*100) + } + return false, nil + case xsclient.TaskStatusTypeSuccess: + return true, nil + case xsclient.TaskStatusTypeFailure: + errorInfo, err := c.client.Task.GetErrorInfo(c.session, task) + if err != nil { + errorInfo = []string{fmt.Sprintf("furthermore, failed to get error info: %s", err.Error())} + } + return false, fmt.Errorf("Task failed: %s", errorInfo) + case xsclient.TaskStatusTypeCancelling, xsclient.TaskStatusTypeCancelled: + return false, fmt.Errorf("Task cancelled") + default: + return false, fmt.Errorf("Unknown task status %v", status) + } + }, + PredicateInterval: 1 * time.Second, + Timeout: 24 * time.Hour, + }.Wait(state) + + resp.Body.Close() + + if err != nil { + err = fmt.Errorf("Error uploading: %s", err.Error()) + return + } + + result, err = c.client.Task.GetResult(c.session, task) + if err != nil { + err = fmt.Errorf("Error getting result: %s", err.Error()) + return + } + + log.Printf("Upload complete") + return +} diff --git a/builder/xenserver/common/interruptible_wait.go b/builder/xenserver/common/interruptible_wait.go new file mode 100644 index 0000000..5658802 --- /dev/null +++ b/builder/xenserver/common/interruptible_wait.go @@ -0,0 +1,86 @@ +package common + +import ( + "github.com/hashicorp/packer-plugin-sdk/multistep" + "time" +) + +type InterruptibleWait struct { + Timeout time.Duration + + // optional: + Predicate func() (result bool, err error) + PredicateInterval time.Duration +} + +type TimeoutError struct{} + +func (err TimeoutError) Error() string { + return "Timed out" +} + +type InterruptedError struct{} + +func (err InterruptedError) Error() string { + return "Interrupted" +} + +type PredicateResult struct { + complete bool + err error +} + +/* Wait waits for up to Timeout duration, checking an optional Predicate every PredicateInterval duration. + The first run of Predicate is immediately after Wait is called. + If the command is interrupted by the user, then an InterruptedError is returned. + If Predicate is not nil, a timeout leads to TimeoutError being returned, and a successful Predicate run leads to nil being returned. + If Predicate is nil, a timeout is not an error, and nil is returned. +*/ +func (wait InterruptibleWait) Wait(state multistep.StateBag) error { + predicateResult := make(chan PredicateResult, 1) + stopWaiting := make(chan struct{}) + defer close(stopWaiting) + + if wait.Predicate != nil { + go func() { + for { + if complete, err := wait.Predicate(); err != nil || complete { + predicateResult <- PredicateResult{complete, err} + return + } + + select { + case <-time.After(wait.PredicateInterval): + continue + case <-stopWaiting: + return + } + } + }() + } + + timeout := time.After(wait.Timeout) + for { + // wait for either install to complete/error, + // an interrupt to come through, or a timeout to occur + + if _, ok := state.GetOk(multistep.StateCancelled); ok { + return InterruptedError{} + } + + select { + case result := <-predicateResult: + return result.err + + case <-time.After(1 * time.Second): + continue + + case <-timeout: + if wait.Predicate != nil { + return TimeoutError{} + } else { + return nil + } + } + } +} diff --git a/builder/xenserver/common/ssh.go b/builder/xenserver/common/ssh.go new file mode 100644 index 0000000..1f3d136 --- /dev/null +++ b/builder/xenserver/common/ssh.go @@ -0,0 +1,217 @@ +package common + +import ( + "bytes" + "encoding/pem" + "fmt" + "io" + "io/ioutil" + "log" + "net" + "os" + "strings" + + "github.com/hashicorp/packer-plugin-sdk/multistep" + gossh "golang.org/x/crypto/ssh" +) + +func SSHAddress(state multistep.StateBag) (string, error) { + sshIP := state.Get("ssh_address").(string) + sshHostPort := 22 + return fmt.Sprintf("%s:%d", sshIP, sshHostPort), nil +} + +func SSHLocalAddress(state multistep.StateBag) (string, error) { + sshLocalPort, ok := state.Get("local_ssh_port").(uint) + if !ok { + return "", fmt.Errorf("SSH port forwarding hasn't been set up yet") + } + conn_str := fmt.Sprintf("%s:%d", "127.0.0.1", sshLocalPort) + return conn_str, nil +} + +func SSHPort(state multistep.StateBag) (int, error) { + sshHostPort := state.Get("local_ssh_port").(uint) + return int(sshHostPort), nil +} + +func CommHost(state multistep.StateBag) (string, error) { + return "127.0.0.1", nil +} + +func SSHConfigFunc(config SSHConfig) func(multistep.StateBag) (*gossh.ClientConfig, error) { + return func(state multistep.StateBag) (*gossh.ClientConfig, error) { + config := state.Get("commonconfig").(CommonConfig) + auth := []gossh.AuthMethod{ + gossh.Password(config.SSHPassword), + } + + if config.SSHKeyPath != "" { + signer, err := FileSigner(config.SSHKeyPath) + if err != nil { + return nil, err + } + + auth = append(auth, gossh.PublicKeys(signer)) + } + + return &gossh.ClientConfig{ + User: config.SSHUser, + Auth: auth, + HostKeyCallback: gossh.InsecureIgnoreHostKey(), + }, nil + } +} + +func doExecuteSSHCmd(cmd, target string, config *gossh.ClientConfig) (stdout string, err error) { + client, err := gossh.Dial("tcp", target, config) + if err != nil { + return "", err + } + + //Create session + session, err := client.NewSession() + if err != nil { + return "", err + } + + defer session.Close() + + var b bytes.Buffer + session.Stdout = &b + if err := session.Run(cmd); err != nil { + return "", err + } + + return strings.Trim(b.String(), "\n"), nil +} + +func ExecuteHostSSHCmd(state multistep.StateBag, cmd string) (stdout string, err error) { + config := state.Get("commonconfig").(CommonConfig) + sshAddress, _ := SSHAddress(state) + // Setup connection config + sshConfig := &gossh.ClientConfig{ + User: config.Username, + Auth: []gossh.AuthMethod{ + gossh.Password(config.Password), + }, + HostKeyCallback: gossh.InsecureIgnoreHostKey(), + } + return doExecuteSSHCmd(cmd, sshAddress, sshConfig) +} + +func ExecuteGuestSSHCmd(state multistep.StateBag, cmd string) (stdout string, err error) { + config := state.Get("commonconfig").(CommonConfig) + localAddress, err := SSHLocalAddress(state) + if err != nil { + return + } + sshConfig, err := SSHConfigFunc(config.SSHConfig)(state) + if err != nil { + return + } + + return doExecuteSSHCmd(cmd, localAddress, sshConfig) +} + +func forward(local_conn net.Conn, config *gossh.ClientConfig, server, remote_dest string, remote_port uint) error { + defer local_conn.Close() + + ssh_client_conn, err := gossh.Dial("tcp", server+":22", config) + if err != nil { + log.Printf("local ssh.Dial error: %s", err) + return err + } + defer ssh_client_conn.Close() + + remote_loc := fmt.Sprintf("%s:%d", remote_dest, remote_port) + ssh_conn, err := ssh_client_conn.Dial("tcp", remote_loc) + if err != nil { + log.Printf("ssh.Dial error: %s", err) + return err + } + defer ssh_conn.Close() + + txDone := make(chan struct{}) + rxDone := make(chan struct{}) + + go func() { + _, err = io.Copy(ssh_conn, local_conn) + if err != nil { + log.Printf("io.copy failed: %v", err) + } + close(txDone) + }() + + go func() { + _, err = io.Copy(local_conn, ssh_conn) + if err != nil { + log.Printf("io.copy failed: %v", err) + } + close(rxDone) + }() + + <-txDone + <-rxDone + + return nil +} + +func ssh_port_forward(local_listener net.Listener, remote_port int, remote_dest, host, username, password string) error { + + config := &gossh.ClientConfig{ + User: username, + Auth: []gossh.AuthMethod{ + gossh.Password(password), + }, + HostKeyCallback: gossh.InsecureIgnoreHostKey(), + } + + for { + local_connection, err := local_listener.Accept() + + if err != nil { + log.Printf("Local accept failed: %s", err) + return err + } + + // Forward to a remote port + go forward(local_connection, config, host, remote_dest, uint(remote_port)) + } + + return nil +} + +// FileSigner returns an gossh.Signer for a key file. +func FileSigner(path string) (gossh.Signer, error) { + f, err := os.Open(path) + if err != nil { + return nil, err + } + defer f.Close() + + keyBytes, err := ioutil.ReadAll(f) + if err != nil { + return nil, err + } + + // We parse the private key on our own first so that we can + // show a nicer error if the private key has a password. + block, _ := pem.Decode(keyBytes) + if block == nil { + return nil, fmt.Errorf( + "Failed to read key '%s': no key found", path) + } + if block.Headers["Proc-Type"] == "4,ENCRYPTED" { + return nil, fmt.Errorf( + "Failed to read key '%s': password protected keys are\n"+ + "not supported. Please decrypt the key prior to use.", path) + } + + signer, err := gossh.ParsePrivateKey(keyBytes) + if err != nil { + return nil, fmt.Errorf("Error setting up SSH config: %s", err) + } + + return signer, nil +} diff --git a/builder/xenserver/common/ssh_config.go b/builder/xenserver/common/ssh_config.go new file mode 100644 index 0000000..3b0fbd7 --- /dev/null +++ b/builder/xenserver/common/ssh_config.go @@ -0,0 +1,48 @@ +package common + +import ( + "errors" + "time" + + "github.com/hashicorp/packer-plugin-sdk/communicator" + "github.com/hashicorp/packer-plugin-sdk/template/interpolate" +) + +type SSHConfig struct { + Comm communicator.Config `mapstructure:",squash"` + + SSHHostPortMin uint `mapstructure:"ssh_host_port_min"` + SSHHostPortMax uint `mapstructure:"ssh_host_port_max"` + SSHSkipNatMapping bool `mapstructure:"ssh_skip_nat_mapping"` + + // These are deprecated, but we keep them around for BC + // TODO(@mitchellh): remove + SSHKeyPath string `mapstructure:"ssh_key_path"` + SSHWaitTimeout time.Duration `mapstructure:"ssh_wait_timeout"` +} + +func (c *SSHConfig) Prepare(ctx *interpolate.Context) []error { + if c.SSHHostPortMin == 0 { + c.SSHHostPortMin = 2222 + } + + if c.SSHHostPortMax == 0 { + c.SSHHostPortMax = 4444 + } + + // TODO: backwards compatibility, write fixer instead + if c.SSHKeyPath != "" { + c.Comm.SSHPrivateKeyFile = c.SSHKeyPath + } + if c.SSHWaitTimeout != 0 { + c.Comm.SSHTimeout = c.SSHWaitTimeout + } + + errs := c.Comm.Prepare(ctx) + if c.SSHHostPortMin > c.SSHHostPortMax { + errs = append(errs, + errors.New("ssh_host_port_min must be less than ssh_host_port_max")) + } + + return errs +} diff --git a/builder/xenserver/common/step_attach_vdi.go b/builder/xenserver/common/step_attach_vdi.go new file mode 100644 index 0000000..0f42bd8 --- /dev/null +++ b/builder/xenserver/common/step_attach_vdi.go @@ -0,0 +1,84 @@ +package common + +import ( + "context" + "fmt" + "log" + + "github.com/hashicorp/packer-plugin-sdk/multistep" + "github.com/hashicorp/packer-plugin-sdk/packer" + xsclient "github.com/terra-farm/go-xen-api-client" +) + +type StepAttachVdi struct { + VdiUuidKey string + VdiType xsclient.VbdType + + vdi xsclient.VDIRef +} + +func (self *StepAttachVdi) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction { + ui := state.Get("ui").(packer.Ui) + c := state.Get("client").(*Connection) + + log.Printf("Running attach vdi for key %s\n", self.VdiUuidKey) + var vdiUuid string + if vdiUuidRaw, ok := state.GetOk(self.VdiUuidKey); ok { + vdiUuid = vdiUuidRaw.(string) + } else { + log.Printf("Skipping attach of '%s'", self.VdiUuidKey) + return multistep.ActionContinue + } + + var err error + self.vdi, err = c.client.VDI.GetByUUID(c.session, vdiUuid) + if err != nil { + ui.Error(fmt.Sprintf("Unable to get VDI from UUID '%s': %s", vdiUuid, err.Error())) + return multistep.ActionHalt + } + + uuid := state.Get("instance_uuid").(string) + instance, err := c.client.VM.GetByUUID(c.session, uuid) + if err != nil { + ui.Error(fmt.Sprintf("Unable to get VM from UUID '%s': %s", uuid, err.Error())) + return multistep.ActionHalt + } + + err = ConnectVdi(c, instance, self.vdi, self.VdiType) + if err != nil { + ui.Error(fmt.Sprintf("Error attaching VDI '%s': '%s'", vdiUuid, err.Error())) + return multistep.ActionHalt + } + + log.Printf("Attached VDI '%s'", vdiUuid) + + return multistep.ActionContinue +} + +func (self *StepAttachVdi) Cleanup(state multistep.StateBag) { + config := state.Get("commonconfig").(CommonConfig) + c := state.Get("client").(*Connection) + if config.ShouldKeepVM(state) { + return + } + + if self.vdi == "" { + return + } + + uuid := state.Get("instance_uuid").(string) + vmRef, err := c.client.VM.GetByUUID(c.session, uuid) + if err != nil { + log.Printf("Unable to get VM from UUID '%s': %s", uuid, err.Error()) + return + } + + vdiUuid := state.Get(self.VdiUuidKey).(string) + + err = DisconnectVdi(c, vmRef, self.vdi) + if err != nil { + log.Printf("Unable to disconnect VDI '%s': %s", vdiUuid, err.Error()) + return + } + log.Printf("Detached VDI '%s'", vdiUuid) +} diff --git a/builder/xenserver/common/step_boot_wait.go b/builder/xenserver/common/step_boot_wait.go new file mode 100644 index 0000000..c084f2e --- /dev/null +++ b/builder/xenserver/common/step_boot_wait.go @@ -0,0 +1,33 @@ +package common + +import ( + "context" + "fmt" + + "github.com/hashicorp/packer-plugin-sdk/multistep" + "github.com/hashicorp/packer-plugin-sdk/packer" +) + +type StepBootWait struct{} + +func (self *StepBootWait) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction { + c := state.Get("client").(*Connection) + config := state.Get("commonconfig").(CommonConfig) + ui := state.Get("ui").(packer.Ui) + + instance, _ := c.client.VM.GetByUUID(c.session, state.Get("instance_uuid").(string)) + ui.Say("Unpausing VM " + state.Get("instance_uuid").(string)) + Unpause(c, instance) + + if int64(config.BootWait) > 0 { + ui.Say(fmt.Sprintf("Waiting %s for boot...", config.BootWait)) + err := InterruptibleWait{Timeout: config.BootWait}.Wait(state) + if err != nil { + ui.Error(err.Error()) + return multistep.ActionHalt + } + } + return multistep.ActionContinue +} + +func (self *StepBootWait) Cleanup(state multistep.StateBag) {} diff --git a/builder/xenserver/common/step_detach_vdi.go b/builder/xenserver/common/step_detach_vdi.go new file mode 100644 index 0000000..8348eeb --- /dev/null +++ b/builder/xenserver/common/step_detach_vdi.go @@ -0,0 +1,53 @@ +package common + +import ( + "context" + "fmt" + "log" + + "github.com/hashicorp/packer-plugin-sdk/multistep" + "github.com/hashicorp/packer-plugin-sdk/packer" +) + +type StepDetachVdi struct { + VdiUuidKey string +} + +func (self *StepDetachVdi) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction { + ui := state.Get("ui").(packer.Ui) + c := state.Get("client").(*Connection) + + var vdiUuid string + if vdiUuidRaw, ok := state.GetOk(self.VdiUuidKey); ok { + vdiUuid = vdiUuidRaw.(string) + } else { + log.Printf("Skipping detach of '%s'", self.VdiUuidKey) + return multistep.ActionContinue + } + + vdi, err := c.client.VDI.GetByUUID(c.session, vdiUuid) + if err != nil { + ui.Error(fmt.Sprintf("Unable to get VDI from UUID '%s': %s", vdiUuid, err.Error())) + return multistep.ActionHalt + } + + uuid := state.Get("instance_uuid").(string) + instance, err := c.client.VM.GetByUUID(c.session, uuid) + if err != nil { + ui.Error(fmt.Sprintf("Unable to get VM from UUID '%s': %s", uuid, err.Error())) + return multistep.ActionHalt + } + + err = DisconnectVdi(c, instance, vdi) + if err != nil { + ui.Error(fmt.Sprintf("Unable to detach VDI '%s': %s", vdiUuid, err.Error())) + //return multistep.ActionHalt + return multistep.ActionContinue + } + + log.Printf("Detached VDI '%s'", vdiUuid) + + return multistep.ActionContinue +} + +func (self *StepDetachVdi) Cleanup(state multistep.StateBag) {} diff --git a/builder/xenserver/common/step_export.go b/builder/xenserver/common/step_export.go new file mode 100644 index 0000000..3bec569 --- /dev/null +++ b/builder/xenserver/common/step_export.go @@ -0,0 +1,278 @@ +package common + +import ( + "context" + "crypto/tls" + "fmt" + "io" + "net/http" + "os" + "os/exec" + + "github.com/hashicorp/packer-plugin-sdk/multistep" + "github.com/hashicorp/packer-plugin-sdk/packer" +) + +type StepExport struct{} + +func downloadFile(url, filename string, ui packer.Ui) (err error) { + + // Create the file + fh, err := os.Create(filename) + if err != nil { + return err + } + defer fh.Close() + + // Define a new transport which allows self-signed certs + tr := &http.Transport{ + TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, + } + + // Create a client + client := &http.Client{Transport: tr} + + // Create request and download file + + resp, err := client.Get(url) + if err != nil { + return err + } + + defer resp.Body.Close() + + var progress uint + var total uint + var percentage uint + var marker_len uint + + progress = uint(0) + total = uint(resp.ContentLength) + percentage = uint(0) + marker_len = uint(5) + + var buffer [4096]byte + for { + n, err := resp.Body.Read(buffer[:]) + if err != nil && err != io.EOF { + return err + } + + progress += uint(n) + + if _, write_err := fh.Write(buffer[:n]); write_err != nil { + return write_err + } + + if err == io.EOF { + break + } + + // Increment percentage in multiples of marker_len + cur_percentage := ((progress * 100 / total) / marker_len) * marker_len + if cur_percentage > percentage { + percentage = cur_percentage + ui.Message(fmt.Sprintf("Downloading... %d%%", percentage)) + } + + } + + return nil +} + +func (StepExport) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction { + config := state.Get("commonconfig").(CommonConfig) + ui := state.Get("ui").(packer.Ui) + c := state.Get("client").(*Connection) + instance_uuid := state.Get("instance_uuid").(string) + suffix := ".vhd" + extrauri := "&format=vhd" + + instance, err := c.client.VM.GetByUUID(c.session, instance_uuid) + if err != nil { + ui.Error(fmt.Sprintf("Could not get VM with UUID '%s': %s", instance_uuid, err.Error())) + return multistep.ActionHalt + } + + if len(config.ExportNetworkNames) > 0 { + vifs, err := c.client.VM.GetVIFs(c.session, instance) + if err != nil { + ui.Error(fmt.Sprintf("Error occured getting VIFs: %s", err.Error())) + return multistep.ActionHalt + } + + for _, vif := range vifs { + err := c.client.VIF.Destroy(c.session, vif) + if err != nil { + ui.Error(fmt.Sprintf("Destroy vif fail: '%s': %s", vif, err.Error())) + return multistep.ActionHalt + } + } + for i, networkNameLabel := range config.ExportNetworkNames { + networks, err := c.client.Network.GetByNameLabel(c.session, networkNameLabel) + + if err != nil { + ui.Error(fmt.Sprintf("Error occured getting Network by name-label: %s", err.Error())) + return multistep.ActionHalt + } + + switch { + case len(networks) == 0: + ui.Error(fmt.Sprintf("Couldn't find a network with the specified name-label '%s'. Aborting.", networkNameLabel)) + return multistep.ActionHalt + case len(networks) > 1: + ui.Error(fmt.Sprintf("Found more than one network with the name '%s'. The name must be unique. Aborting.", networkNameLabel)) + return multistep.ActionHalt + } + + //we need the VIF index string + vifIndexString := fmt.Sprintf("%d", i) + _, err = ConnectNetwork(c, networks[0], instance, vifIndexString) + + if err != nil { + ui.Say(err.Error()) + } + } + } + + ui.Say("Step: export artifact") + + compress_option_xe := "compress=false" + compress_option_url := "" + + switch config.Format { + case "none": + ui.Say("Skipping export") + return multistep.ActionContinue + + case "xva_compressed": + compress_option_xe = "compress=true" + compress_option_url = "use_compression=true&" + fallthrough + case "xva": + // export the VM + + export_filename := fmt.Sprintf("%s/%s.xva", config.OutputDir, config.VMName) + + use_xe := os.Getenv("USE_XE") == "1" + if xe, e := exec.LookPath("xe"); e == nil && use_xe { + cmd := exec.Command( + xe, + "-s", c.Host, + "-p", "443", + "-u", c.Username, + "-pw", c.Password, + "vm-export", + "vm="+instance_uuid, + compress_option_xe, + "filename="+export_filename, + ) + + ui.Say(fmt.Sprintf("Getting XVA %+v %+v", cmd.Path, cmd.Args)) + + err = cmd.Run() + } else { + export_url := fmt.Sprintf("https://%s/export?%suuid=%s&session_id=%s", + c.Host, + compress_option_url, + instance_uuid, + c.GetSession(), + ) + + ui.Say("Getting XVA " + export_url) + err = downloadFile(export_url, export_filename, ui) + } + + if err != nil { + ui.Error(fmt.Sprintf("Could not download XVA: %s", err.Error())) + return multistep.ActionHalt + } + + case "vdi_raw": + suffix = ".raw" + extrauri = "" + fallthrough + case "vdi_vhd": + // export the disks + + disks, err := GetDisks(c, instance) + if err != nil { + ui.Error(fmt.Sprintf("Could not get VM disks: %s", err.Error())) + return multistep.ActionHalt + } + for _, disk := range disks { + disk_uuid, err := c.client.VDI.GetUUID(c.session, disk) + if err != nil { + ui.Error(fmt.Sprintf("Could not get disk with UUID '%s': %s", disk_uuid, err.Error())) + return multistep.ActionHalt + } + + // Work out XenServer version + hosts, err := c.client.Host.GetAll(c.session) + + if err != nil { + ui.Error(fmt.Sprintf("Could not retrieve hosts in the pool: %s", err.Error())) + return multistep.ActionHalt + } + host := hosts[0] + host_software_versions, err := c.client.Host.GetSoftwareVersion(c.session, host) + xs_version := host_software_versions["product_version"] + + if err != nil { + ui.Error(fmt.Sprintf("Could not get the software version: %s", err.Error())) + return multistep.ActionHalt + } + + var disk_export_url string + + // @todo: check for 6.5 SP1 + if xs_version <= "6.5.0" && config.Format == "vdi_vhd" { + // Export the VHD using a Transfer VM + + disk_export_url, err = Expose(c, disk, "vhd") + + if err != nil { + ui.Error(fmt.Sprintf("Failed to expose disk %s: %s", disk_uuid, err.Error())) + return multistep.ActionHalt + } + + } else { + + // Use the preferred direct export from XAPI + // Basic auth in URL request is required as session token is not + // accepted for some reason. + // @todo: raise with XAPI team. + disk_export_url = fmt.Sprintf("https://%s:%s@%s/export_raw_vdi?vdi=%s%s", + c.Username, + c.Password, + c.Host, + disk_uuid, + extrauri) + + } + + disk_export_filename := fmt.Sprintf("%s/%s%s", config.OutputDir, disk_uuid, suffix) + + ui.Say("Getting VDI " + disk_export_url) + err = downloadFile(disk_export_url, disk_export_filename, ui) + if err != nil { + ui.Error(fmt.Sprintf("Could not download VDI: %s", err.Error())) + return multistep.ActionHalt + } + + // Call unexpose in case a TVM was used. The call is harmless + // if that is not the case. + Unexpose(c, disk) + + } + + default: + panic(fmt.Sprintf("Unknown export format '%s'", config.Format)) + } + + ui.Say("Download completed: " + config.OutputDir) + + return multistep.ActionContinue +} + +func (StepExport) Cleanup(state multistep.StateBag) {} diff --git a/builder/xenserver/common/step_find_vdi.go b/builder/xenserver/common/step_find_vdi.go new file mode 100644 index 0000000..9638ecb --- /dev/null +++ b/builder/xenserver/common/step_find_vdi.go @@ -0,0 +1,49 @@ +package common + +import ( + "context" + "fmt" + + "github.com/hashicorp/packer-plugin-sdk/multistep" + "github.com/hashicorp/packer-plugin-sdk/packer" +) + +type StepFindVdi struct { + VdiName string + ImagePathFunc func() string + VdiUuidKey string +} + +func (self *StepFindVdi) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction { + ui := state.Get("ui").(packer.Ui) + c := state.Get("client").(*Connection) + + // Ignore if VdiName is not specified + if self.VdiName == "" { + return multistep.ActionContinue + } + + vdis, err := c.client.VDI.GetByNameLabel(c.session, self.VdiName) + + switch { + case len(vdis) == 0: + ui.Error(fmt.Sprintf("Couldn't find a VDI named '%s'", self.VdiName)) + return multistep.ActionHalt + case len(vdis) > 1: + ui.Error(fmt.Sprintf("Found more than one VDI with name '%s'. Name must be unique", self.VdiName)) + return multistep.ActionHalt + } + + vdi := vdis[0] + + vdiUuid, err := c.client.VDI.GetUUID(c.session, vdi) + if err != nil { + ui.Error(fmt.Sprintf("Unable to get UUID of VDI '%s': %s", self.VdiName, err.Error())) + return multistep.ActionHalt + } + state.Put(self.VdiUuidKey, vdiUuid) + + return multistep.ActionContinue +} + +func (self *StepFindVdi) Cleanup(state multistep.StateBag) {} diff --git a/builder/xenserver/common/step_forward_port_over_ssh.go b/builder/xenserver/common/step_forward_port_over_ssh.go new file mode 100644 index 0000000..cf06e5d --- /dev/null +++ b/builder/xenserver/common/step_forward_port_over_ssh.go @@ -0,0 +1,50 @@ +package common + +import ( + "context" + "fmt" + + "github.com/hashicorp/packer-plugin-sdk/multistep" + "github.com/hashicorp/packer-plugin-sdk/packer" +) + +type StepForwardPortOverSSH struct { + RemotePort func(state multistep.StateBag) (int, error) + RemoteDest func(state multistep.StateBag) (string, error) + + HostPortMin uint + HostPortMax uint + + ResultKey string +} + +func (self *StepForwardPortOverSSH) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction { + + config := state.Get("commonconfig").(CommonConfig) + ui := state.Get("ui").(packer.Ui) + + // Find a free local port: + + l, sshHostPort := FindPort(self.HostPortMin, self.HostPortMax) + + if l == nil || sshHostPort == 0 { + ui.Error("Error: unable to find free host port. Try providing a larger range [host_port_min, host_port_max]") + return multistep.ActionHalt + } + + ui.Say(fmt.Sprintf("Creating a local port forward over SSH on local port %d", sshHostPort)) + + hostAddress, _ := state.Get("ssh_address").(string) + remotePort, _ := self.RemotePort(state) + remoteDest, _ := self.RemoteDest(state) + + go ssh_port_forward(l, remotePort, remoteDest, hostAddress, config.Username, config.Password) + ui.Say(fmt.Sprintf("Port forward setup. %d ---> %s:%d on %s", sshHostPort, remoteDest, remotePort, hostAddress)) + + // Provide the local port to future steps. + state.Put(self.ResultKey, sshHostPort) + + return multistep.ActionContinue +} + +func (self *StepForwardPortOverSSH) Cleanup(state multistep.StateBag) {} diff --git a/builder/xenserver/common/step_get_vnc_port.go b/builder/xenserver/common/step_get_vnc_port.go new file mode 100644 index 0000000..b4bac56 --- /dev/null +++ b/builder/xenserver/common/step_get_vnc_port.go @@ -0,0 +1,51 @@ +package common + +import ( + "fmt" + "strconv" + + "github.com/hashicorp/packer-plugin-sdk/multistep" + "github.com/hashicorp/packer-plugin-sdk/packer" +) + +type StepGetVNCPort struct{} + +func (self *StepGetVNCPort) Run(state multistep.StateBag) multistep.StepAction { + ui := state.Get("ui").(packer.Ui) + + ui.Say("Step: forward the instances VNC port over SSH") + + domid := state.Get("domid").(int) + cmd := fmt.Sprintf("xenstore-read /local/domain/%d/console/vnc-port", domid) + + remote_vncport, err := ExecuteHostSSHCmd(state, cmd) + if err != nil { + ui.Error(fmt.Sprintf("Unable to get VNC port (is the VM running?): %s", err.Error())) + return multistep.ActionHalt + } + + remote_port, err := strconv.ParseUint(remote_vncport, 10, 16) + + if err != nil { + ui.Error(fmt.Sprintf("Unable to convert '%s' to an int", remote_vncport)) + ui.Error(err.Error()) + return multistep.ActionHalt + } + + state.Put("instance_vnc_port", uint(remote_port)) + + return multistep.ActionContinue +} + +func (self *StepGetVNCPort) Cleanup(state multistep.StateBag) { +} + +func InstanceVNCPort(state multistep.StateBag) (uint, error) { + vncPort := state.Get("instance_vnc_port").(uint) + return vncPort, nil +} + +func InstanceVNCIP(state multistep.StateBag) (string, error) { + // The port is in Dom0, so we want to forward from localhost + return "127.0.0.1", nil +} diff --git a/builder/xenserver/common/step_http_server.go b/builder/xenserver/common/step_http_server.go new file mode 100644 index 0000000..bedf317 --- /dev/null +++ b/builder/xenserver/common/step_http_server.go @@ -0,0 +1,96 @@ +package common + +// Taken from hashicorp/packer/builder/qemu/step_http_server.go + +import ( + "context" + "fmt" + "log" + "net" + "net/http" + + "github.com/hashicorp/packer-plugin-sdk/multistep" + "github.com/hashicorp/packer-plugin-sdk/packer" +) + +// This step creates and runs the HTTP server that is serving files from the +// directory specified by the 'http_directory` configuration parameter in the +// template. +// +// Uses: +// config *config +// ui packer.Ui +// +// Produces: +// http_port int - The port the HTTP server started on. +type StepHTTPServer struct { + Chan chan<- string + + l net.Listener +} + +type IPSnooper struct { + ch chan<- string + handler http.Handler +} + +func (snooper IPSnooper) ServeHTTP(resp http.ResponseWriter, req *http.Request) { + log.Printf("HTTP: %s %s %s", req.RemoteAddr, req.Method, req.URL) + ip, _, err := net.SplitHostPort(req.RemoteAddr) + if err == nil && ip != "" { + select { + case snooper.ch <- ip: + log.Printf("Remembering remote address '%s'", ip) + default: + // if ch is already full, don't block waiting to send the address, just drop it + } + } + snooper.handler.ServeHTTP(resp, req) +} + +func (s *StepHTTPServer) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction { + config := state.Get("commonconfig").(CommonConfig) + ui := state.Get("ui").(packer.Ui) + + var httpPort uint = 0 + if config.HTTPDir == "" { + // the packer provision steps assert this type is an int + // so this cannot be a uint like the rest of the code + state.Put("http_port", int(httpPort)) + return multistep.ActionContinue + } + + s.l, httpPort = FindPort(config.HTTPPortMin, config.HTTPPortMax) + + if s.l == nil || httpPort == 0 { + ui.Error("Error: unable to find free HTTP server port. Try providing a larger range [http_port_min, http_port_max]") + return multistep.ActionHalt + } + + ui.Say(fmt.Sprintf("Starting HTTP server on port %d", httpPort)) + + // Start the HTTP server and run it in the background + fileServer := http.FileServer(http.Dir(config.HTTPDir)) + server := &http.Server{ + Addr: fmt.Sprintf(":%d", httpPort), + Handler: IPSnooper{ + ch: s.Chan, + handler: fileServer, + }, + } + go server.Serve(s.l) + + // Save the address into the state so it can be accessed in the future + // the packer provision steps assert this type is an int + // so this cannot be a uint like the rest of the code + state.Put("http_port", int(httpPort)) + + return multistep.ActionContinue +} + +func (s *StepHTTPServer) Cleanup(multistep.StateBag) { + if s.l != nil { + // Close the listener so that the HTTP server stops + s.l.Close() + } +} diff --git a/builder/xenserver/common/step_prepare_output_dir.go b/builder/xenserver/common/step_prepare_output_dir.go new file mode 100644 index 0000000..44e6565 --- /dev/null +++ b/builder/xenserver/common/step_prepare_output_dir.go @@ -0,0 +1,54 @@ +package common + +/* Taken from https://raw.githubusercontent.com/hashicorp/packer/master/builder/qemu/step_prepare_output_dir.go */ + +import ( + "context" + "log" + "os" + "time" + + "github.com/hashicorp/packer-plugin-sdk/multistep" + "github.com/hashicorp/packer-plugin-sdk/packer" +) + +type StepPrepareOutputDir struct { + Force bool + Path string +} + +func (self *StepPrepareOutputDir) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction { + ui := state.Get("ui").(packer.Ui) + + if _, err := os.Stat(self.Path); err == nil && self.Force { + ui.Say("Deleting previous output directory...") + os.RemoveAll(self.Path) + } + + if err := os.MkdirAll(self.Path, 0755); err != nil { + state.Put("error", err) + return multistep.ActionHalt + } + + return multistep.ActionContinue +} + +func (self *StepPrepareOutputDir) Cleanup(state multistep.StateBag) { + _, cancelled := state.GetOk(multistep.StateCancelled) + _, halted := state.GetOk(multistep.StateHalted) + + if cancelled || halted { + ui := state.Get("ui").(packer.Ui) + + ui.Say("Deleting output directory...") + for i := 0; i < 5; i++ { + err := os.RemoveAll(self.Path) + if err == nil { + break + } + + log.Printf("Error removing output dir: %s", err) + time.Sleep(2 * time.Second) + } + } +} diff --git a/builder/xenserver/common/step_set_vm_host_ssh_address.go b/builder/xenserver/common/step_set_vm_host_ssh_address.go new file mode 100644 index 0000000..33f7f5d --- /dev/null +++ b/builder/xenserver/common/step_set_vm_host_ssh_address.go @@ -0,0 +1,43 @@ +package common + +import ( + "context" + "fmt" + + "github.com/hashicorp/packer-plugin-sdk/multistep" + "github.com/hashicorp/packer-plugin-sdk/packer" +) + +type StepSetVmHostSshAddress struct{} + +func (self *StepSetVmHostSshAddress) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction { + + c := state.Get("client").(*Connection) + ui := state.Get("ui").(packer.Ui) + + ui.Say("Step: Set SSH address to VM host IP") + + uuid := state.Get("instance_uuid").(string) + instance, err := c.client.VM.GetByUUID(c.session, uuid) + if err != nil { + ui.Error(fmt.Sprintf("Unable to get VM from UUID '%s': %s", uuid, err.Error())) + return multistep.ActionHalt + } + + host, err := c.client.VM.GetResidentOn(c.session, instance) + if err != nil { + ui.Error(fmt.Sprintf("Unable to get VM Host for VM '%s': %s", uuid, err.Error())) + } + + address, err := c.client.Host.GetAddress(c.session, host) + if err != nil { + ui.Error(fmt.Sprintf("Unable to get address from VM Host: %s", err.Error())) + } + + state.Put("ssh_address", address) + ui.Say(fmt.Sprintf("Set host SSH address to '%s'.", address)) + + return multistep.ActionContinue +} + +func (self *StepSetVmHostSshAddress) Cleanup(state multistep.StateBag) {} diff --git a/builder/xenserver/common/step_set_vm_to_template.go b/builder/xenserver/common/step_set_vm_to_template.go new file mode 100644 index 0000000..1bf6358 --- /dev/null +++ b/builder/xenserver/common/step_set_vm_to_template.go @@ -0,0 +1,35 @@ +package common + +import ( + "context" + "fmt" + + "github.com/hashicorp/packer-plugin-sdk/multistep" + "github.com/hashicorp/packer-plugin-sdk/packer" +) + +type StepSetVmToTemplate struct{} + +func (StepSetVmToTemplate) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction { + ui := state.Get("ui").(packer.Ui) + c := state.Get("client").(*Connection) + instance_uuid := state.Get("instance_uuid").(string) + + instance, err := c.client.VM.GetByUUID(c.session, instance_uuid) + if err != nil { + ui.Error(fmt.Sprintf("Could not get VM with UUID '%s': %s", instance_uuid, err.Error())) + return multistep.ActionHalt + } + + err = c.client.VM.SetIsATemplate(c.session, instance, true) + + if err != nil { + ui.Error(fmt.Sprintf("failed to set VM '%s' as a template with error: %v", instance_uuid, err)) + return multistep.ActionHalt + } + + ui.Message("Successfully set VM as a template") + return multistep.ActionContinue +} + +func (StepSetVmToTemplate) Cleanup(state multistep.StateBag) {} diff --git a/builder/xenserver/common/step_shutdown.go b/builder/xenserver/common/step_shutdown.go new file mode 100644 index 0000000..2cafad3 --- /dev/null +++ b/builder/xenserver/common/step_shutdown.go @@ -0,0 +1,82 @@ +package common + +import ( + "context" + "fmt" + "time" + + "github.com/hashicorp/packer-plugin-sdk/multistep" + "github.com/hashicorp/packer-plugin-sdk/packer" + xenapi "github.com/terra-farm/go-xen-api-client" +) + +type StepShutdown struct{} + +func (StepShutdown) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction { + config := state.Get("commonconfig").(CommonConfig) + ui := state.Get("ui").(packer.Ui) + c := state.Get("client").(*Connection) + instance_uuid := state.Get("instance_uuid").(string) + + instance, err := c.client.VM.GetByUUID(c.session, instance_uuid) + if err != nil { + ui.Error(fmt.Sprintf("Could not get VM with UUID '%s': %s", instance_uuid, err.Error())) + return multistep.ActionHalt + } + + ui.Say("Step: Shutting down VM") + + // Shutdown the VM + success := func() bool { + if config.ShutdownCommand != "" { + ui.Message("Executing shutdown command...") + + _, err := ExecuteGuestSSHCmd(state, config.ShutdownCommand) + if err != nil { + ui.Error(fmt.Sprintf("Shutdown command failed: %s", err.Error())) + return false + } + + ui.Message("Waiting for VM to enter Halted state...") + + err = InterruptibleWait{ + Predicate: func() (bool, error) { + power_state, err := c.client.VM.GetPowerState(c.session, instance) + return power_state == xenapi.VMPowerStateHalted, err + }, + PredicateInterval: 5 * time.Second, + Timeout: 300 * time.Second, + }.Wait(state) + + if err != nil { + ui.Error(fmt.Sprintf("Error waiting for VM to halt: %s", err.Error())) + return false + } + + } else { + ui.Message("Attempting to cleanly shutdown the VM...") + + err = c.client.VM.CleanShutdown(c.session, instance) + if err != nil { + ui.Error(fmt.Sprintf("Could not shut down VM: %s", err.Error())) + return false + } + + } + return true + }() + + if !success { + ui.Say("WARNING: Forcing hard shutdown of the VM...") + err = c.client.VM.HardShutdown(c.session, instance) + if err != nil { + ui.Error(fmt.Sprintf("Could not hard shut down VM -- giving up: %s", err.Error())) + return multistep.ActionHalt + } + } + + ui.Message("Successfully shut down VM") + return multistep.ActionContinue +} + +func (StepShutdown) Cleanup(state multistep.StateBag) {} diff --git a/builder/xenserver/common/step_start_on_himn.go b/builder/xenserver/common/step_start_on_himn.go new file mode 100644 index 0000000..d5a13b3 --- /dev/null +++ b/builder/xenserver/common/step_start_on_himn.go @@ -0,0 +1,137 @@ +package common + +import ( + "fmt" + "log" + "time" + + "github.com/hashicorp/packer-plugin-sdk/multistep" + "github.com/hashicorp/packer-plugin-sdk/packer" + gossh "golang.org/x/crypto/ssh" +) + +type StepStartOnHIMN struct{} + +/* + * This step starts the installed guest on the Host Internal Management Network + * as there exists an API to obtain the IP allocated to the VM by XAPI. + * This in turn will allow Packer to SSH into the VM, provided NATing has been + * enabled on the host. + * + */ + +func (self *StepStartOnHIMN) Run(state multistep.StateBag) multistep.StepAction { + + ui := state.Get("ui").(packer.Ui) + c := state.Get("client").(*Connection) + + ui.Say("Step: Start VM on the Host Internal Mangement Network") + + uuid := state.Get("instance_uuid").(string) + instance, err := c.client.VM.GetByUUID(c.session, uuid) + if err != nil { + ui.Error(fmt.Sprintf("Unable to get VM from UUID '%s': %s", uuid, err.Error())) + return multistep.ActionHalt + } + + // Find the HIMN Ref + networks, err := c.client.Network.GetByNameLabel(c.session, "Host internal management network") + if err != nil || len(networks) == 0 { + ui.Error("Unable to find a host internal management network") + ui.Error(err.Error()) + return multistep.ActionHalt + } + + himn := networks[0] + + // Create a VIF for the HIMN + himn_vif, err := ConnectNetwork(c, himn, instance, "0") + if err != nil { + ui.Error("Error creating VIF") + ui.Error(err.Error()) + return multistep.ActionHalt + } + + // Start the VM + c.client.VM.Start(c.session, instance, false, false) + + var himn_iface_ip string = "" + + // Obtain the allocated IP + err = InterruptibleWait{ + Predicate: func() (found bool, err error) { + ips, err := c.client.Network.GetAssignedIps(c.session, himn) + if err != nil { + return false, fmt.Errorf("Can't get assigned IPs: %s", err.Error()) + } + log.Printf("IPs: %s", ips) + log.Printf("Ref: %s", instance) + + //Check for instance.Ref in map + if vm_ip, ok := ips[*himn_vif]; ok && vm_ip != "" { + ui.Say("Found the VM's IP: " + vm_ip) + himn_iface_ip = vm_ip + return true, nil + } + + ui.Say("Wait for IP address...") + return false, nil + }, + PredicateInterval: 10 * time.Second, + Timeout: 100 * time.Second, + }.Wait(state) + + if err != nil { + ui.Error(fmt.Sprintf("Unable to find an IP on the Host-internal management interface: %s", err.Error())) + return multistep.ActionHalt + } + + state.Put("himn_ssh_address", himn_iface_ip) + ui.Say("Stored VM's IP " + himn_iface_ip) + + // Wait for the VM to boot, and check we can ping this interface + + ping_cmd := fmt.Sprintf("ping -c 1 %s", himn_iface_ip) + + err = InterruptibleWait{ + Predicate: func() (success bool, err error) { + ui.Message(fmt.Sprintf("Attempting to ping interface: %s", ping_cmd)) + _, err = ExecuteHostSSHCmd(state, ping_cmd) + + switch err.(type) { + case nil: + // ping succeeded + return true, nil + case *gossh.ExitError: + // ping failed, try again + return false, nil + default: + // unknown error + return false, err + } + }, + PredicateInterval: 10 * time.Second, + Timeout: 300 * time.Second, + }.Wait(state) + + if err != nil { + ui.Error(fmt.Sprintf("Unable to ping interface. (Has the VM not booted?): %s", err.Error())) + return multistep.ActionHalt + } + + ui.Message("Ping success! Continuing...") + return multistep.ActionContinue + +} + +func (self *StepStartOnHIMN) Cleanup(state multistep.StateBag) {} + +func HimnSSHIP(state multistep.StateBag) (string, error) { + ip := state.Get("himn_ssh_address").(string) + return ip, nil +} + +func HimnSSHPort(state multistep.StateBag) (uint, error) { + config := state.Get("commonconfig").(CommonConfig) + return config.SSHPort, nil +} diff --git a/builder/xenserver/common/step_start_vm_paused.go b/builder/xenserver/common/step_start_vm_paused.go new file mode 100644 index 0000000..b8cd1ee --- /dev/null +++ b/builder/xenserver/common/step_start_vm_paused.go @@ -0,0 +1,58 @@ +package common + +import ( + "context" + "fmt" + + "github.com/hashicorp/packer-plugin-sdk/multistep" + "github.com/hashicorp/packer-plugin-sdk/packer" +) + +type StepStartVmPaused struct { + VmCleanup +} + +func (self *StepStartVmPaused) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction { + + c := state.Get("client").(*Connection) + ui := state.Get("ui").(packer.Ui) + config := state.Get("config").(Config) + + ui.Say("Step: Start VM Paused") + + uuid := state.Get("instance_uuid").(string) + instance, err := c.client.VM.GetByUUID(c.session, uuid) + if err != nil { + ui.Error(fmt.Sprintf("Unable to get VM from UUID '%s': %s", uuid, err.Error())) + return multistep.ActionHalt + } + + // note that here "cd" means boot from hard drive ('c') first, then CDROM ('d') + err = c.client.VM.SetHVMBootPolicy(c.session, instance, "BIOS order") + + if err != nil { + ui.Error(fmt.Sprintf("Unable to set HVM boot params: %s", err.Error())) + return multistep.ActionHalt + } + + err = c.client.VM.SetHVMBootParams(c.session, instance, map[string]string{"order": "cd", "firmware": config.Firmware}) + if err != nil { + ui.Error(fmt.Sprintf("Unable to set HVM boot params: %s", err.Error())) + return multistep.ActionHalt + } + + err = c.client.VM.Start(c.session, instance, true, false) + if err != nil { + ui.Error(fmt.Sprintf("Unable to start VM with UUID '%s': %s", uuid, err.Error())) + return multistep.ActionHalt + } + + domid, err := c.client.VM.GetDomid(c.session, instance) + if err != nil { + ui.Error(fmt.Sprintf("Unable to get domid of VM with UUID '%s': %s", uuid, err.Error())) + return multistep.ActionHalt + } + state.Put("domid", domid) + + return multistep.ActionContinue +} diff --git a/builder/xenserver/common/step_type_boot_command.go b/builder/xenserver/common/step_type_boot_command.go new file mode 100644 index 0000000..00689ac --- /dev/null +++ b/builder/xenserver/common/step_type_boot_command.go @@ -0,0 +1,272 @@ +package common + +/* Heavily borrowed from builder/quemu/step_type_boot_command.go */ + +import ( + "context" + "crypto/tls" + "fmt" + "io" + "log" + "net" + "strings" + "time" + "unicode" + "unicode/utf8" + + "github.com/hashicorp/packer-plugin-sdk/multistep" + "github.com/hashicorp/packer-plugin-sdk/packer" + "github.com/hashicorp/packer-plugin-sdk/template/interpolate" + "github.com/mitchellh/go-vnc" +) + +const KeyLeftShift uint = 0xFFE1 + +type bootCommandTemplateData struct { + Name string + HTTPIP string + HTTPPort uint +} + +type StepTypeBootCommand struct { + Ctx interpolate.Context +} + +func (self *StepTypeBootCommand) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction { + config := state.Get("commonconfig").(CommonConfig) + ui := state.Get("ui").(packer.Ui) + c := state.Get("client").(*Connection) + httpPort := state.Get("http_port").(int) + + // skip this step if we have nothing to type + if len(config.BootCommand) == 0 { + return multistep.ActionContinue + } + + vmRef, err := c.client.VM.GetByNameLabel(c.session, config.VMName) + + if err != nil { + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } + + if len(vmRef) != 1 { + ui.Error(fmt.Sprintf("expected to find a single VM, instead found '%d'. Ensure the VM name is unique", len(vmRef))) + } + + consoles, err := c.client.VM.GetConsoles(c.session, vmRef[0]) + if err != nil { + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } + + if len(consoles) != 1 { + ui.Error(fmt.Sprintf("expected to find a VM console, instead found '%d'. Ensure there is only one console", len(consoles))) + return multistep.ActionHalt + } + + location, err := c.client.Console.GetLocation(c.session, consoles[0]) + if err != nil { + ui.Error(err.Error()) + return multistep.ActionHalt + } + locationPieces := strings.SplitAfter(location, "/") + consoleHost := strings.TrimSuffix(locationPieces[2], "/") + ui.Say(fmt.Sprintf("Connecting to the VM console VNC over xapi via %s", consoleHost)) + conn, err := net.Dial("tcp", fmt.Sprintf("%s:443", consoleHost)) + + if err != nil { + err := fmt.Errorf("Error connecting to VNC: %s", err) + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } + + defer conn.Close() + + tlsConfig := &tls.Config{ + InsecureSkipVerify: true, + } + tlsConn := tls.Client(conn, tlsConfig) + + consoleLocation := strings.TrimSpace(fmt.Sprintf("/%s", locationPieces[len(locationPieces)-1])) + httpReq := fmt.Sprintf("CONNECT %s HTTP/1.0\r\nHost: %s\r\nCookie: session_id=%s\r\n\r\n", consoleLocation, consoleHost, c.session) + fmt.Printf("Sending the follow http req: %v", httpReq) + + ui.Say(fmt.Sprintf("Making HTTP request to initiate VNC connection: %s", httpReq)) + _, err = io.WriteString(tlsConn, httpReq) + + if err != nil { + err := fmt.Errorf("failed to start vnc session: %v", err) + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } + + buffer := make([]byte, 10000) + _, err = tlsConn.Read(buffer) + if err != nil && err != io.EOF { + err := fmt.Errorf("failed to read vnc session response: %v", err) + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } + + ui.Say(fmt.Sprintf("Received response: %s", string(buffer))) + + vncClient, err := vnc.Client(tlsConn, &vnc.ClientConfig{Exclusive: true}) + + if err != nil { + err := fmt.Errorf("Error establishing VNC session: %s", err) + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } + + defer vncClient.Close() + + log.Printf("Connected to the VNC console: %s", vncClient.DesktopName) + + // find local ip + envVar, err := ExecuteHostSSHCmd(state, "echo $SSH_CLIENT") + if err != nil { + ui.Error(fmt.Sprintf("Error detecting local IP: %s", err)) + return multistep.ActionHalt + } + if envVar == "" { + ui.Error("Error detecting local IP: $SSH_CLIENT was empty") + return multistep.ActionHalt + } + localIp := strings.Split(envVar, " ")[0] + ui.Message(fmt.Sprintf("Found local IP: %s", localIp)) + + self.Ctx.Data = &bootCommandTemplateData{ + config.VMName, + localIp, + uint(httpPort), + } + + ui.Say("Typing boot commands over VNC...") + for _, command := range config.BootCommand { + + command, err := interpolate.Render(command, &self.Ctx) + if err != nil { + err := fmt.Errorf("Error preparing boot command: %s", err) + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } + + // Check for interrupts + if _, ok := state.GetOk(multistep.StateCancelled); ok { + return multistep.ActionHalt + } + + vncSendString(vncClient, command) + } + + ui.Say("Finished typing.") + + return multistep.ActionContinue +} + +func (self *StepTypeBootCommand) Cleanup(multistep.StateBag) {} + +// Taken from qemu's builder plugin - not an exported function. +func vncSendString(c *vnc.ClientConn, original string) { + // Scancodes reference: https://github.com/qemu/qemu/blob/master/ui/vnc_keysym.h + special := make(map[string]uint32) + special[""] = 0xFF08 + special[""] = 0xFFFF + special[""] = 0xFF0D + special[""] = 0xFF1B + special[""] = 0xFFBE + special[""] = 0xFFBF + special[""] = 0xFFC0 + special[""] = 0xFFC1 + special[""] = 0xFFC2 + special[""] = 0xFFC3 + special[""] = 0xFFC4 + special[""] = 0xFFC5 + special[""] = 0xFFC6 + special[""] = 0xFFC7 + special[""] = 0xFFC8 + special[""] = 0xFFC9 + special[""] = 0xFF0D + special[""] = 0xFF09 + special[""] = 0xFF52 + special[""] = 0xFF54 + special[""] = 0xFF51 + special[""] = 0xFF53 + special[""] = 0x020 + special[""] = 0xFF63 + special[""] = 0xFF50 + special[""] = 0xFF57 + special[""] = 0xFF55 + special[""] = 0xFF56 + + shiftedChars := "~!@#$%^&*()_+{}|:\"<>?" + + // TODO(mitchellh): Ripe for optimizations of some point, perhaps. + for len(original) > 0 { + var keyCode uint32 + keyShift := false + + if strings.HasPrefix(original, "") { + log.Printf("Special code '' found, sleeping one second") + time.Sleep(1 * time.Second) + original = original[len(""):] + continue + } + + if strings.HasPrefix(original, "") { + log.Printf("Special code '' found, sleeping 5 seconds") + time.Sleep(5 * time.Second) + original = original[len(""):] + continue + } + + if strings.HasPrefix(original, "") { + log.Printf("Special code '' found, sleeping 10 seconds") + time.Sleep(10 * time.Second) + original = original[len(""):] + continue + } + + for specialCode, specialValue := range special { + if strings.HasPrefix(original, specialCode) { + log.Printf("Special code '%s' found, replacing with: %d", specialCode, specialValue) + keyCode = specialValue + original = original[len(specialCode):] + break + } + } + + if keyCode == 0 { + r, size := utf8.DecodeRuneInString(original) + original = original[size:] + keyCode = uint32(r) + keyShift = unicode.IsUpper(r) || strings.ContainsRune(shiftedChars, r) + + log.Printf("Sending char '%c', code %d, shift %v", r, keyCode, keyShift) + } + + if keyShift { + c.KeyEvent(uint32(KeyLeftShift), true) + } + + c.KeyEvent(keyCode, true) + time.Sleep(time.Second / 10) + c.KeyEvent(keyCode, false) + time.Sleep(time.Second / 10) + + if keyShift { + c.KeyEvent(uint32(KeyLeftShift), false) + } + + // no matter what, wait a small period + time.Sleep(50 * time.Millisecond) + } +} diff --git a/builder/xenserver/common/step_upload_vdi.go b/builder/xenserver/common/step_upload_vdi.go new file mode 100644 index 0000000..67a00f3 --- /dev/null +++ b/builder/xenserver/common/step_upload_vdi.go @@ -0,0 +1,145 @@ +package common + +import ( + "context" + "fmt" + "log" + "os" + "time" + + "github.com/hashicorp/packer-plugin-sdk/multistep" + "github.com/hashicorp/packer-plugin-sdk/packer" + xenapi "github.com/terra-farm/go-xen-api-client" +) + +type StepUploadVdi struct { + VdiNameFunc func() string + ImagePathFunc func() string + VdiUuidKey string +} + +func (self *StepUploadVdi) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction { + config := state.Get("commonconfig").(CommonConfig) + ui := state.Get("ui").(packer.Ui) + c := state.Get("client").(*Connection) + + imagePath := self.ImagePathFunc() + vdiName := self.VdiNameFunc() + if imagePath == "" { + // skip if no disk image to attach + return multistep.ActionContinue + } + + ui.Say(fmt.Sprintf("Step: Upload VDI '%s'", vdiName)) + + // Create VDI for the image + srs, err := c.client.SR.GetAll(c.session) + ui.Say(fmt.Sprintf("Step: Found SRs '%v'", srs)) + + sr, err := config.GetISOSR(c) + + if err != nil { + ui.Error(fmt.Sprintf("Unable to get SR: %v", err)) + return multistep.ActionHalt + } + + // Open the file for reading (NB: HTTPUpload closes the file for us) + fh, err := os.Open(imagePath) + if err != nil { + ui.Error(fmt.Sprintf("Unable to open disk image '%s': %s", imagePath, err.Error())) + return multistep.ActionHalt + } + + // Get file length + fstat, err := fh.Stat() + if err != nil { + ui.Error(fmt.Sprintf("Unable to stat disk image '%s': %s", imagePath, err.Error())) + return multistep.ActionHalt + } + fileLength := fstat.Size() + + // Create the VDI + // vdi, err := sr.CreateVdi(vdiName, fileLength) + vdi, err := c.client.VDI.Create(c.session, xenapi.VDIRecord{ + NameLabel: vdiName, + VirtualSize: int(fileLength), + Type: "user", + Sharable: false, + ReadOnly: false, + SR: sr, + OtherConfig: map[string]string{ + "temp": "temp", + }, + }) + if err != nil { + ui.Error(fmt.Sprintf("Unable to create VDI '%s': %s", vdiName, err.Error())) + return multistep.ActionHalt + } + + vdiUuid, err := c.client.VDI.GetUUID(c.session, vdi) + if err != nil { + ui.Error(fmt.Sprintf("Unable to get UUID of VDI '%s': %s", vdiName, err.Error())) + return multistep.ActionHalt + } + state.Put(self.VdiUuidKey, vdiUuid) + + _, err = HTTPUpload(fmt.Sprintf("https://%s/import_raw_vdi?vdi=%s&session_id=%s", + c.Host, + vdi, + c.GetSession(), + ), fh, state) + if err != nil { + ui.Error(fmt.Sprintf("Unable to upload VDI: %s", err.Error())) + return multistep.ActionHalt + } + + return multistep.ActionContinue +} + +func (self *StepUploadVdi) Cleanup(state multistep.StateBag) { + config := state.Get("commonconfig").(CommonConfig) + ui := state.Get("ui").(packer.Ui) + c := state.Get("client").(*Connection) + + vdiName := self.VdiNameFunc() + + if config.ShouldKeepVM(state) { + return + } + + vdiUuidRaw, ok := state.GetOk(self.VdiUuidKey) + if !ok { + // VDI doesn't exist + return + } + + vdiUuid := vdiUuidRaw.(string) + if vdiUuid == "" { + // VDI already cleaned up + return + } + + vdi, err := c.client.VDI.GetByUUID(c.session, vdiUuid) + if err != nil { + ui.Error(fmt.Sprintf("Can't get VDI '%s': %s", vdiUuid, err.Error())) + return + } + + // an interrupted import_raw_vdi takes a while to release the VDI + // so try several times + for i := 0; i < 3; i++ { + log.Printf("Trying to destroy VDI...") + err = c.client.VDI.Destroy(c.session, vdi) + if err == nil { + break + } + time.Sleep(1 * time.Second) + } + if err != nil { + ui.Error(fmt.Sprintf("Can't destroy VDI '%s': %s", vdiUuid, err.Error())) + return + } + ui.Say(fmt.Sprintf("Destroyed VDI '%s'", vdiName)) + + state.Put(self.VdiUuidKey, "") +} diff --git a/builder/xenserver/common/step_wait_for_ip.go b/builder/xenserver/common/step_wait_for_ip.go new file mode 100644 index 0000000..bed9735 --- /dev/null +++ b/builder/xenserver/common/step_wait_for_ip.go @@ -0,0 +1,96 @@ +package common + +import ( + "context" + "fmt" + "time" + + "github.com/hashicorp/packer-plugin-sdk/multistep" + "github.com/hashicorp/packer-plugin-sdk/packer" +) + +type StepWaitForIP struct { + VmCleanup + Chan <-chan string + Timeout time.Duration +} + +func (self *StepWaitForIP) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction { + ui := state.Get("ui").(packer.Ui) + c := state.Get("client").(*Connection) + config := state.Get("commonconfig").(CommonConfig) + + ui.Say("Step: Wait for VM's IP to become known to us.") + + uuid := state.Get("instance_uuid").(string) + instance, err := c.client.VM.GetByUUID(c.session, uuid) + if err != nil { + ui.Error(fmt.Sprintf("Unable to get VM from UUID '%s': %s", uuid, err.Error())) + return multistep.ActionHalt + } + + var ip string + err = InterruptibleWait{ + Timeout: self.Timeout, + PredicateInterval: 5 * time.Second, + Predicate: func() (result bool, err error) { + + if config.IPGetter == "auto" || config.IPGetter == "http" { + + // Snoop IP from HTTP fetch + select { + case ip = <-self.Chan: + ui.Message(fmt.Sprintf("Got IP '%s' from HTTP request", ip)) + return true, nil + default: + } + + } + + if config.IPGetter == "auto" || config.IPGetter == "tools" { + + // Look for PV IP + m, err := c.client.VM.GetGuestMetrics(c.session, instance) + if err != nil { + return false, err + } + if m != "" { + metrics, err := c.client.VMGuestMetrics.GetRecord(c.session, m) + if err != nil { + return false, err + } + networks := metrics.Networks + var ok bool + if ip, ok = networks["0/ip"]; ok { + if ip != "" { + ui.Message(fmt.Sprintf("Got IP '%s' from XenServer tools", ip)) + return true, nil + } + } + } + + } + + return false, nil + }, + }.Wait(state) + if err != nil { + ui.Error(fmt.Sprintf("Could not get IP address of VM: %s", err.Error())) + // @todo: give advice on what went wrong (no HTTP server? no PV drivers?) + return multistep.ActionHalt + } + + ui.Say(fmt.Sprintf("Got IP address '%s'", ip)) + state.Put("instance_ssh_address", ip) + + return multistep.ActionContinue +} + +func InstanceSSHIP(state multistep.StateBag) (string, error) { + ip := state.Get("instance_ssh_address").(string) + return ip, nil +} + +func InstanceSSHPort(state multistep.StateBag) (int, error) { + return 22, nil +} diff --git a/builder/xenserver/common/vm_cleanup.go b/builder/xenserver/common/vm_cleanup.go new file mode 100644 index 0000000..0c2c59c --- /dev/null +++ b/builder/xenserver/common/vm_cleanup.go @@ -0,0 +1,31 @@ +package common + +import ( + "fmt" + "log" + + "github.com/hashicorp/packer-plugin-sdk/multistep" +) + +type VmCleanup struct{} + +func (self *VmCleanup) Cleanup(state multistep.StateBag) { + config := state.Get("commonconfig").(CommonConfig) + c := state.Get("client").(*Connection) + + if config.ShouldKeepVM(state) { + return + } + + uuid := state.Get("instance_uuid").(string) + instance, err := c.client.VM.GetByUUID(c.session, uuid) + if err != nil { + log.Printf(fmt.Sprintf("Unable to get VM from UUID '%s': %s", uuid, err.Error())) + return + } + + err = c.client.VM.HardShutdown(c.session, instance) + if err != nil { + log.Printf(fmt.Sprintf("Unable to force shutdown VM '%s': %s", uuid, err.Error())) + } +} diff --git a/builder/xenserver/iso/builder.go b/builder/xenserver/iso/builder.go new file mode 100644 index 0000000..a9e37f1 --- /dev/null +++ b/builder/xenserver/iso/builder.go @@ -0,0 +1,325 @@ +package iso + +import ( + "context" + "errors" + "fmt" + "path" + "strings" + "time" + + "github.com/hashicorp/hcl/v2/hcldec" + "github.com/hashicorp/packer-plugin-sdk/communicator" + "github.com/hashicorp/packer-plugin-sdk/multistep" + commonsteps "github.com/hashicorp/packer-plugin-sdk/multistep/commonsteps" + "github.com/hashicorp/packer-plugin-sdk/packer" + hconfig "github.com/hashicorp/packer-plugin-sdk/template/config" + "github.com/hashicorp/packer-plugin-sdk/template/interpolate" + xsclient "github.com/terra-farm/go-xen-api-client" + xscommon "github.com/xenserver/packer-builder-xenserver/builder/xenserver/common" +) + +type Builder struct { + config xscommon.Config + runner multistep.Runner +} + +func (self *Builder) ConfigSpec() hcldec.ObjectSpec { return self.config.FlatMapstructure().HCL2Spec() } + +func (self *Builder) Prepare(raws ...interface{}) (params []string, warns []string, retErr error) { + + var errs *packer.MultiError + + err := hconfig.Decode(&self.config, &hconfig.DecodeOpts{ + Interpolate: true, + InterpolateFilter: &interpolate.RenderFilter{ + Exclude: []string{ + "boot_command", + }, + }, + }, raws...) + + if err != nil { + packer.MultiErrorAppend(errs, err) + } + + errs = packer.MultiErrorAppend( + errs, self.config.CommonConfig.Prepare(self.config.GetInterpContext(), &self.config.PackerConfig)...) + errs = packer.MultiErrorAppend(errs, self.config.SSHConfig.Prepare(self.config.GetInterpContext())...) + + // Set default values + + if self.config.RawInstallTimeout == "" { + self.config.RawInstallTimeout = "200m" + } + + if self.config.DiskSize == 0 { + self.config.DiskSize = 40000 + } + + if self.config.VCPUsMax == 0 { + self.config.VCPUsMax = 1 + } + + if self.config.VCPUsAtStartup == 0 { + self.config.VCPUsAtStartup = 1 + } + + if self.config.VCPUsAtStartup > self.config.VCPUsMax { + self.config.VCPUsAtStartup = self.config.VCPUsMax + } + + if self.config.VMMemory == 0 { + self.config.VMMemory = 1024 + } + + if self.config.CloneTemplate == "" { + self.config.CloneTemplate = "Other install media" + } + + if self.config.Firmware == "" { + self.config.Firmware = "bios" + } + + if len(self.config.PlatformArgs) == 0 { + pargs := make(map[string]string) + pargs["viridian"] = "false" + pargs["nx"] = "true" + pargs["pae"] = "true" + pargs["apic"] = "true" + pargs["timeoffset"] = "0" + pargs["acpi"] = "1" + self.config.PlatformArgs = pargs + } + + // Template substitution + + templates := map[string]*string{ + "clone_template": &self.config.CloneTemplate, + "iso_checksum": &self.config.ISOChecksum, + "iso_checksum_type": &self.config.ISOChecksumType, + "iso_url": &self.config.ISOUrl, + "iso_name": &self.config.ISOName, + "install_timeout": &self.config.RawInstallTimeout, + } + for i := range self.config.ISOUrls { + templates[fmt.Sprintf("iso_urls[%d]", i)] = &self.config.ISOUrls[i] + } + + // Validation + + self.config.InstallTimeout, err = time.ParseDuration(self.config.RawInstallTimeout) + if err != nil { + errs = packer.MultiErrorAppend( + errs, fmt.Errorf("Failed to parse install_timeout: %s", err)) + } + + if self.config.ISOName == "" { + + // If ISO name is not specified, assume a URL and checksum has been provided. + + if self.config.ISOChecksumType == "" { + errs = packer.MultiErrorAppend( + errs, errors.New("The iso_checksum_type must be specified.")) + } else { + self.config.ISOChecksumType = strings.ToLower(self.config.ISOChecksumType) + if self.config.ISOChecksumType != "none" { + if self.config.ISOChecksum == "" { + errs = packer.MultiErrorAppend( + errs, errors.New("Due to the file size being large, an iso_checksum is required.")) + } else { + self.config.ISOChecksum = strings.ToLower(self.config.ISOChecksum) + } + } + } + + if len(self.config.ISOUrls) == 0 { + if self.config.ISOUrl == "" { + errs = packer.MultiErrorAppend( + errs, errors.New("One of iso_url or iso_urls must be specified.")) + } else { + self.config.ISOUrls = []string{self.config.ISOUrl} + } + } else if self.config.ISOUrl != "" { + errs = packer.MultiErrorAppend( + errs, errors.New("Only one of iso_url or iso_urls may be specified.")) + } + } else { + + // An ISO name has been provided. It should be attached from an available SR. + + } + + if len(errs.Errors) > 0 { + retErr = errors.New(errs.Error()) + } + + return nil, nil, retErr + +} + +func (self *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (packer.Artifact, error) { + c, err := xscommon.NewXenAPIClient(self.config.HostIp, self.config.Username, self.config.Password) + + if err != nil { + return nil, err + } + ui.Say("XAPI client session established") + + c.GetClient().Host.GetAll(c.GetSessionRef()) + + //Share state between the other steps using a statebag + state := new(multistep.BasicStateBag) + state.Put("client", c) + state.Put("config", self.config) + state.Put("commonconfig", self.config.CommonConfig) + state.Put("hook", hook) + state.Put("ui", ui) + + httpReqChan := make(chan string, 1) + + //Build the steps + download_steps := []multistep.Step{ + &commonsteps.StepDownload{ + Checksum: self.config.ISOChecksum, + Description: "ISO", + ResultKey: "iso_path", + Url: self.config.ISOUrls, + }, + } + + steps := []multistep.Step{ + &xscommon.StepPrepareOutputDir{ + Force: self.config.PackerForce, + Path: self.config.OutputDir, + }, + &commonsteps.StepCreateFloppy{ + Files: self.config.FloppyFiles, + Label: "cidata", + }, + &xscommon.StepHTTPServer{ + Chan: httpReqChan, + }, + &xscommon.StepUploadVdi{ + VdiNameFunc: func() string { + return "Packer-floppy-disk" + }, + ImagePathFunc: func() string { + if floppyPath, ok := state.GetOk("floppy_path"); ok { + return floppyPath.(string) + } + return "" + }, + VdiUuidKey: "floppy_vdi_uuid", + }, + &xscommon.StepUploadVdi{ + VdiNameFunc: func() string { + if len(self.config.ISOUrls) > 0 { + return path.Base(self.config.ISOUrls[0]) + } + return "" + }, + ImagePathFunc: func() string { + if isoPath, ok := state.GetOk("iso_path"); ok { + return isoPath.(string) + } + return "" + }, + VdiUuidKey: "iso_vdi_uuid", + }, + &xscommon.StepFindVdi{ + VdiName: self.config.ToolsIsoName, + VdiUuidKey: "tools_vdi_uuid", + }, + &xscommon.StepFindVdi{ + VdiName: self.config.ISOName, + VdiUuidKey: "isoname_vdi_uuid", + }, + new(stepCreateInstance), + &xscommon.StepAttachVdi{ + VdiUuidKey: "floppy_vdi_uuid", + VdiType: xsclient.VbdTypeFloppy, + }, + &xscommon.StepAttachVdi{ + VdiUuidKey: "iso_vdi_uuid", + VdiType: xsclient.VbdTypeCD, + }, + &xscommon.StepAttachVdi{ + VdiUuidKey: "isoname_vdi_uuid", + VdiType: xsclient.VbdTypeCD, + }, + &xscommon.StepAttachVdi{ + VdiUuidKey: "tools_vdi_uuid", + VdiType: xsclient.VbdTypeCD, + }, + new(xscommon.StepStartVmPaused), + new(xscommon.StepSetVmHostSshAddress), + // &xscommon.StepForwardPortOverSSH{ + // RemotePort: xscommon.InstanceVNCPort, + // RemoteDest: xscommon.InstanceVNCIP, + // HostPortMin: self.config.HostPortMin, + // HostPortMax: self.config.HostPortMax, + // ResultKey: "local_vnc_port", + // }, + new(xscommon.StepBootWait), + &xscommon.StepTypeBootCommand{ + Ctx: *self.config.GetInterpContext(), + }, + &xscommon.StepWaitForIP{ + Chan: httpReqChan, + Timeout: self.config.InstallTimeout, // @todo change this + }, + &xscommon.StepForwardPortOverSSH{ + RemotePort: xscommon.InstanceSSHPort, + RemoteDest: xscommon.InstanceSSHIP, + HostPortMin: self.config.HostPortMin, + HostPortMax: self.config.HostPortMax, + ResultKey: "local_ssh_port", + }, + &communicator.StepConnect{ + Config: &self.config.SSHConfig.Comm, + Host: xscommon.InstanceSSHIP, + SSHConfig: self.config.Comm.SSHConfigFunc(), + SSHPort: xscommon.InstanceSSHPort, + }, + new(commonsteps.StepProvision), + new(xscommon.StepShutdown), + new(xscommon.StepSetVmToTemplate), + &xscommon.StepDetachVdi{ + VdiUuidKey: "iso_vdi_uuid", + }, + &xscommon.StepDetachVdi{ + VdiUuidKey: "isoname_vdi_uuid", + }, + &xscommon.StepDetachVdi{ + VdiUuidKey: "tools_vdi_uuid", + }, + &xscommon.StepDetachVdi{ + VdiUuidKey: "floppy_vdi_uuid", + }, + new(xscommon.StepExport), + } + + if self.config.ISOName == "" { + steps = append(download_steps, steps...) + } + + self.runner = &multistep.BasicRunner{Steps: steps} + self.runner.Run(ctx, state) + + 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.") + } + + artifact, _ := xscommon.NewArtifact(self.config.OutputDir) + + return artifact, nil +} diff --git a/builder/xenserver/iso/builder_test.go b/builder/xenserver/iso/builder_test.go new file mode 100644 index 0000000..dd4df15 --- /dev/null +++ b/builder/xenserver/iso/builder_test.go @@ -0,0 +1,361 @@ +package iso + +import ( + "reflect" + "testing" + + "github.com/hashicorp/packer-plugin-sdk/packer" +) + +func testConfig() map[string]interface{} { + return map[string]interface{}{ + "remote_host": "localhost", + "remote_username": "admin", + "remote_password": "admin", + "vm_name": "foo", + "iso_checksum": "foo", + "iso_checksum_type": "md5", + "iso_url": "http://www.google.com/", + "shutdown_command": "yes", + "ssh_username": "foo", + + packer.BuildNameConfigKey: "foo", + } +} + +func TestBuilder_ImplementsBuilder(t *testing.T) { + var raw interface{} + raw = &Builder{} + if _, ok := raw.(packer.Builder); !ok { + t.Error("Builder must implement builder.") + } +} + +func TestBuilderPrepare_Defaults(t *testing.T) { + var b Builder + config := testConfig() + _, warns, err := b.Prepare(config) + if len(warns) > 0 { + t.Fatalf("bad: %#v", warns) + } + if err != nil { + t.Fatalf("should not have error: %s", err) + } + + if b.config.ToolsIsoName != "xs-tools.iso" { + t.Errorf("bad tools ISO name: %s", b.config.ToolsIsoName) + } + + if b.config.CloneTemplate != "Other install media" { + t.Errorf("bad clone template: %s", b.config.CloneTemplate) + } + + if b.config.VMName == "" { + t.Errorf("bad vm name: %s", b.config.VMName) + } + + if b.config.Format != "xva" { + t.Errorf("bad format: %s", b.config.Format) + } + + if b.config.KeepVM != "never" { + t.Errorf("bad keep instance: %s", b.config.KeepVM) + } +} + +func TestBuilderPrepare_DiskSize(t *testing.T) { + var b Builder + config := testConfig() + + delete(config, "disk_size") + _, warns, err := b.Prepare(config) + if len(warns) > 0 { + t.Fatalf("bad: %#v", warns) + } + if err != nil { + t.Fatalf("bad err: %s", err) + } + + if b.config.DiskSize != 40000 { + t.Fatalf("bad size: %d", b.config.DiskSize) + } + + config["disk_size"] = 60000 + b = Builder{} + _, warns, err = b.Prepare(config) + if len(warns) > 0 { + t.Fatalf("bad: %#v", warns) + } + if err != nil { + t.Fatalf("should not have error: %s", err) + } + + if b.config.DiskSize != 60000 { + t.Fatalf("bad size: %d", b.config.DiskSize) + } +} + +func TestBuilderPrepare_Format(t *testing.T) { + var b Builder + config := testConfig() + + // Bad + config["format"] = "foo" + _, warns, err := b.Prepare(config) + if len(warns) > 0 { + t.Fatalf("bad: %#v", warns) + } + if err == nil { + t.Fatal("should have error") + } + + // Good + config["format"] = "vdi_raw" + b = Builder{} + _, warns, err = b.Prepare(config) + if len(warns) > 0 { + t.Fatalf("bad: %#v", warns) + } + if err != nil { + t.Fatalf("should not have error: %s", err) + } +} + +func TestBuilderPrepare_HTTPPort(t *testing.T) { + var b Builder + config := testConfig() + + // Bad + config["http_port_min"] = 1000 + config["http_port_max"] = 500 + _, warns, err := b.Prepare(config) + if len(warns) > 0 { + t.Fatalf("bad: %#v", warns) + } + if err == nil { + t.Fatal("should have error") + } + + // Bad + config["http_port_min"] = -500 + b = Builder{} + _, warns, err = b.Prepare(config) + if len(warns) > 0 { + t.Fatalf("bad: %#v", warns) + } + if err == nil { + t.Fatal("should have error") + } + + // Good + config["http_port_min"] = 500 + config["http_port_max"] = 1000 + b = Builder{} + _, warns, err = b.Prepare(config) + if len(warns) > 0 { + t.Fatalf("bad: %#v", warns) + } + if err != nil { + t.Fatalf("should not have error: %s", err) + } +} + +func TestBuilderPrepare_InvalidKey(t *testing.T) { + var b Builder + config := testConfig() + + // Add a random key + config["i_should_not_be_valid"] = true + _, warns, err := b.Prepare(config) + if len(warns) > 0 { + t.Fatalf("bad: %#v", warns) + } + if err == nil { + t.Fatal("should have error") + } +} + +func TestBuilderPrepare_ISOChecksum(t *testing.T) { + var b Builder + config := testConfig() + + // Test bad + config["iso_checksum"] = "" + _, warns, err := b.Prepare(config) + if len(warns) > 0 { + t.Fatalf("bad: %#v", warns) + } + if err == nil { + t.Fatal("should have error") + } + + // Test good + config["iso_checksum"] = "FOo" + b = Builder{} + _, warns, err = b.Prepare(config) + if len(warns) > 0 { + t.Fatalf("bad: %#v", warns) + } + if err != nil { + t.Fatalf("should not have error: %s", err) + } + + if b.config.ISOChecksum != "foo" { + t.Fatalf("should've lowercased: %s", b.config.ISOChecksum) + } +} + +func TestBuilderPrepare_ISOChecksumType(t *testing.T) { + var b Builder + config := testConfig() + + // Test bad + config["iso_checksum_type"] = "" + _, warns, err := b.Prepare(config) + if len(warns) > 0 { + t.Fatalf("bad: %#v", warns) + } + if err == nil { + t.Fatal("should have error") + } + + // Test good + config["iso_checksum_type"] = "mD5" + b = Builder{} + _, warns, err = b.Prepare(config) + if len(warns) > 0 { + t.Fatalf("bad: %#v", warns) + } + if err != nil { + t.Fatalf("should not have error: %s", err) + } + + if b.config.ISOChecksumType != "md5" { + t.Fatalf("should've lowercased: %s", b.config.ISOChecksumType) + } + + // Test unknown + config["iso_checksum_type"] = "fake" + b = Builder{} + _, warns, err = b.Prepare(config) + if len(warns) > 0 { + t.Fatalf("bad: %#v", warns) + } + if err == nil { + t.Fatal("should have error") + } + + // Test none + config["iso_checksum_type"] = "none" + b = Builder{} + _, warns, err = b.Prepare(config) + // @todo: give warning in this case? + /* + if len(warns) == 0 { + t.Fatalf("bad: %#v", warns) + } + */ + if err != nil { + t.Fatalf("should not have error: %s", err) + } + + if b.config.ISOChecksumType != "none" { + t.Fatalf("should've lowercased: %s", b.config.ISOChecksumType) + } +} + +func TestBuilderPrepare_ISOUrl(t *testing.T) { + var b Builder + config := testConfig() + delete(config, "iso_url") + delete(config, "iso_urls") + + // Test both epty + config["iso_url"] = "" + b = Builder{} + _, warns, err := b.Prepare(config) + if len(warns) > 0 { + t.Fatalf("bad: %#v", warns) + } + if err == nil { + t.Fatal("should have error") + } + + // Test iso_url set + config["iso_url"] = "http://www.packer.io" + b = Builder{} + _, warns, err = b.Prepare(config) + if len(warns) > 0 { + t.Fatalf("bad: %#v", warns) + } + if err != nil { + t.Errorf("should not have error: %s", err) + } + + expected := []string{"http://www.packer.io"} + if !reflect.DeepEqual(b.config.ISOUrls, expected) { + t.Fatalf("bad: %#v", b.config.ISOUrls) + } + + // Test both set + config["iso_url"] = "http://www.packer.io" + config["iso_urls"] = []string{"http://www.packer.io"} + b = Builder{} + _, warns, err = b.Prepare(config) + if len(warns) > 0 { + t.Fatalf("bad: %#v", warns) + } + if err == nil { + t.Fatal("should have error") + } + + // Test just iso_urls set + delete(config, "iso_url") + config["iso_urls"] = []string{ + "http://www.packer.io", + "http://www.hashicorp.com", + } + + b = Builder{} + _, warns, err = b.Prepare(config) + if len(warns) > 0 { + t.Fatalf("bad: %#v", warns) + } + if err != nil { + t.Errorf("should not have error: %s", err) + } + + expected = []string{ + "http://www.packer.io", + "http://www.hashicorp.com", + } + if !reflect.DeepEqual(b.config.ISOUrls, expected) { + t.Fatalf("bad: %#v", b.config.ISOUrls) + } +} + +func TestBuilderPrepare_KeepVM(t *testing.T) { + var b Builder + config := testConfig() + + // Bad + config["keep_vm"] = "foo" + _, warns, err := b.Prepare(config) + if len(warns) > 0 { + t.Fatalf("bad: %#v", warns) + } + if err == nil { + t.Fatal("should have error") + } + + // Good + config["keep_vm"] = "always" + b = Builder{} + _, warns, err = b.Prepare(config) + if len(warns) > 0 { + t.Fatalf("bad: %#v", warns) + } + if err != nil { + t.Fatalf("should not have error: %s", err) + } +} diff --git a/builder/xenserver/iso/step_create_instance.go b/builder/xenserver/iso/step_create_instance.go new file mode 100644 index 0000000..df6702a --- /dev/null +++ b/builder/xenserver/iso/step_create_instance.go @@ -0,0 +1,251 @@ +package iso + +import ( + "context" + "fmt" + "log" + + "github.com/hashicorp/packer-plugin-sdk/multistep" + "github.com/hashicorp/packer-plugin-sdk/packer" + xenapi "github.com/terra-farm/go-xen-api-client" + xsclient "github.com/terra-farm/go-xen-api-client" + xscommon "github.com/xenserver/packer-builder-xenserver/builder/xenserver/common" +) + +type stepCreateInstance struct { + instance *xsclient.VMRef + vdi *xsclient.VDIRef +} + +func (self *stepCreateInstance) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction { + + c := state.Get("client").(*xscommon.Connection) + config := state.Get("config").(xscommon.Config) + ui := state.Get("ui").(packer.Ui) + + ui.Say("Step: Create Instance") + + // Get the template to clone from + + vms, err := c.GetClient().VM.GetByNameLabel(c.GetSessionRef(), config.CloneTemplate) + + switch { + case len(vms) == 0: + ui.Error(fmt.Sprintf("Couldn't find a template with the name-label '%s'. Aborting.", config.CloneTemplate)) + return multistep.ActionHalt + case len(vms) > 1: + ui.Error(fmt.Sprintf("Found more than one template with the name '%s'. The name must be unique. Aborting.", config.CloneTemplate)) + return multistep.ActionHalt + } + + template := vms[0] + + // Clone that VM template + instance, err := c.GetClient().VM.Clone(c.GetSessionRef(), template, config.VMName) + if err != nil { + ui.Error(fmt.Sprintf("Error cloning VM: %s", err.Error())) + return multistep.ActionHalt + } + self.instance = &instance + + err = c.GetClient().VM.SetIsATemplate(c.GetSessionRef(), instance, false) + if err != nil { + ui.Error(fmt.Sprintf("Error setting is_a_template=false: %s", err.Error())) + return multistep.ActionHalt + } + + err = c.GetClient().VM.SetVCPUsMax(c.GetSessionRef(), instance, int(config.VCPUsMax)) + if err != nil { + ui.Error(fmt.Sprintf("Error setting VM VCPUs Max=%d: %s", config.VCPUsMax, err.Error())) + return multistep.ActionHalt + } + + err = c.GetClient().VM.SetVCPUsAtStartup(c.GetSessionRef(), instance, int(config.VCPUsAtStartup)) + if err != nil { + ui.Error(fmt.Sprintf("Error setting VM VCPUs At Startup=%d: %s", config.VCPUsAtStartup, err.Error())) + return multistep.ActionHalt + } + + memory := int(config.VMMemory * 1024 * 1024) + err = c.GetClient().VM.SetMemoryLimits(c.GetSessionRef(), instance, memory, memory, memory, memory) + if err != nil { + ui.Error(fmt.Sprintf("Error setting VM memory=%d: %s", memory, err.Error())) + return multistep.ActionHalt + } + + err = c.GetClient().VM.SetPlatform(c.GetSessionRef(), instance, config.PlatformArgs) + if err != nil { + ui.Error(fmt.Sprintf("Error setting VM platform: %s", err.Error())) + return multistep.ActionHalt + } + + err = c.GetClient().VM.SetNameDescription(c.GetSessionRef(), instance, config.VMDescription) + if err != nil { + ui.Error(fmt.Sprintf("Error setting VM description: %s", err.Error())) + return multistep.ActionHalt + } + + if len(config.VMOtherConfig) != 0 { + vm_other_config, err := c.GetClient().VM.GetOtherConfig(c.GetSessionRef(), instance) + if err != nil { + ui.Error(fmt.Sprintf("Error getting VM other-config: %s", err.Error())) + return multistep.ActionHalt + } + for key, value := range config.VMOtherConfig { + vm_other_config[key] = value + } + err = c.GetClient().VM.SetOtherConfig(c.GetSessionRef(), instance, vm_other_config) + if err != nil { + ui.Error(fmt.Sprintf("Error setting VM other-config: %s", err.Error())) + return multistep.ActionHalt + } + } + + err = c.GetClient().VM.RemoveFromOtherConfig(c.GetSessionRef(), instance, "disks") + if err != nil { + ui.Error(fmt.Sprintf("Error removing disks from VM other-config: %s", err.Error())) + return multistep.ActionHalt + } + + // Create VDI for the instance + sr, err := config.GetSR(c) + + if err != nil { + ui.Error(fmt.Sprintf("Unable to get SR: %s", err.Error())) + return multistep.ActionHalt + } + + ui.Say(fmt.Sprintf("Using the following SR for the VM: %s", sr)) + + vdi, err := c.GetClient().VDI.Create(c.GetSessionRef(), xenapi.VDIRecord{ + NameLabel: "Packer-disk", + VirtualSize: int(config.DiskSize * 1024 * 1024), + Type: "user", + Sharable: false, + ReadOnly: false, + SR: sr, + OtherConfig: map[string]string{ + "temp": "temp", + }, + }) + if err != nil { + ui.Error(fmt.Sprintf("Unable to create packer disk VDI: %s", err.Error())) + return multistep.ActionHalt + } + self.vdi = &vdi + + err = xscommon.ConnectVdi(c, instance, vdi, xsclient.VbdTypeDisk) + if err != nil { + ui.Error(fmt.Sprintf("Unable to connect packer disk VDI: %s", err.Error())) + return multistep.ActionHalt + } + + // Connect Network + + var network xsclient.NetworkRef + + if len(config.NetworkNames) == 0 { + // No network has be specified. Use the management interface + log.Println("No network name given, attempting to use management interface") + pifs, err := c.GetClient().PIF.GetAll(c.GetSessionRef()) + + if err != nil { + ui.Error(fmt.Sprintf("Error getting PIFs: %s", err.Error())) + return multistep.ActionHalt + } + + for _, pif := range pifs { + pif_rec, err := c.GetClient().PIF.GetRecord(c.GetSessionRef(), pif) + + if err != nil { + ui.Error(fmt.Sprintf("Error getting PIF record: %s", err.Error())) + return multistep.ActionHalt + } + + if pif_rec.Management { + network = pif_rec.Network + } + + } + + if string(network) == "" { + ui.Error("Error: couldn't find management network. Aborting.") + return multistep.ActionHalt + } + + log.Printf("Creating VIF on network '%s' on VM '%s'\n", network, instance) + _, err = xscommon.ConnectNetwork(c, network, instance, "0") + + if err != nil { + ui.Error(fmt.Sprintf("Failed to create VIF with error: %v", err)) + return multistep.ActionHalt + } + + } else { + log.Printf("Using provided network names: %v\n", config.NetworkNames) + // Look up each network by it's name label + for i, networkNameLabel := range config.NetworkNames { + networks, err := c.GetClient().Network.GetByNameLabel(c.GetSessionRef(), networkNameLabel) + + if err != nil { + ui.Error(fmt.Sprintf("Error occured getting Network by name-label: %s", err.Error())) + return multistep.ActionHalt + } + + switch { + case len(networks) == 0: + ui.Error(fmt.Sprintf("Couldn't find a network with the specified name-label '%s'. Aborting.", networkNameLabel)) + return multistep.ActionHalt + case len(networks) > 1: + ui.Error(fmt.Sprintf("Found more than one network with the name '%s'. The name must be unique. Aborting.", networkNameLabel)) + return multistep.ActionHalt + } + + //we need the VIF index string + vifIndexString := fmt.Sprintf("%d", i) + _, err = xscommon.ConnectNetwork(c, networks[0], instance, vifIndexString) + + if err != nil { + ui.Say(fmt.Sprintf("Failed to connect VIF with error: %v", err.Error())) + } + } + } + + instanceId, err := c.GetClient().VM.GetUUID(c.GetSessionRef(), instance) + if err != nil { + ui.Error(fmt.Sprintf("Unable to get VM UUID: %s", err.Error())) + return multistep.ActionHalt + } + + state.Put("instance_uuid", instanceId) + ui.Say(fmt.Sprintf("Created instance '%s'", instanceId)) + + return multistep.ActionContinue +} + +func (self *stepCreateInstance) Cleanup(state multistep.StateBag) { + config := state.Get("config").(xscommon.Config) + if config.ShouldKeepVM(state) { + return + } + + ui := state.Get("ui").(packer.Ui) + c := state.Get("client").(*xscommon.Connection) + + if self.instance != nil { + ui.Say("Destroying VM") + _ = c.GetClient().VM.HardShutdown(c.GetSessionRef(), *self.instance) // redundant, just in case + err := c.GetClient().VM.Destroy(c.GetSessionRef(), *self.instance) + if err != nil { + ui.Error(err.Error()) + } + } + + if self.vdi != nil { + ui.Say("Destroying VDI") + err := c.GetClient().VDI.Destroy(c.GetSessionRef(), *self.vdi) + if err != nil { + ui.Error(err.Error()) + } + } +} diff --git a/builder/xenserver/xva/builder.go b/builder/xenserver/xva/builder.go new file mode 100644 index 0000000..00235d2 --- /dev/null +++ b/builder/xenserver/xva/builder.go @@ -0,0 +1,191 @@ +package xva + +import ( + "context" + "errors" + "fmt" + "time" + + "github.com/hashicorp/hcl/v2/hcldec" + "github.com/hashicorp/packer-plugin-sdk/communicator" + "github.com/hashicorp/packer-plugin-sdk/multistep" + commonsteps "github.com/hashicorp/packer-plugin-sdk/multistep/commonsteps" + "github.com/hashicorp/packer-plugin-sdk/packer" + hconfig "github.com/hashicorp/packer-plugin-sdk/template/config" + "github.com/hashicorp/packer-plugin-sdk/template/interpolate" + xsclient "github.com/terra-farm/go-xen-api-client" + xscommon "github.com/xenserver/packer-builder-xenserver/builder/xenserver/common" +) + +type Builder struct { + config xscommon.Config + runner multistep.Runner +} + +func (self *Builder) ConfigSpec() hcldec.ObjectSpec { return self.config.FlatMapstructure().HCL2Spec() } + +func (self *Builder) Prepare(raws ...interface{}) (params []string, warns []string, retErr error) { + + var errs *packer.MultiError + + err := hconfig.Decode(&self.config, &hconfig.DecodeOpts{ + Interpolate: true, + InterpolateFilter: &interpolate.RenderFilter{ + Exclude: []string{ + "boot_command", + }, + }, + }, raws...) + + if err != nil { + packer.MultiErrorAppend(errs, err) + } + + errs = packer.MultiErrorAppend( + errs, self.config.CommonConfig.Prepare(self.config.GetInterpContext(), &self.config.PackerConfig)...) + + // Set default values + if self.config.VCPUsMax == 0 { + self.config.VCPUsMax = 1 + } + + if self.config.VCPUsAtStartup == 0 { + self.config.VCPUsAtStartup = 1 + } + + if self.config.VCPUsAtStartup > self.config.VCPUsMax { + self.config.VCPUsAtStartup = self.config.VCPUsMax + } + + if self.config.VMMemory == 0 { + self.config.VMMemory = 1024 + } + + if len(self.config.PlatformArgs) == 0 { + pargs := make(map[string]string) + pargs["viridian"] = "false" + pargs["nx"] = "true" + pargs["pae"] = "true" + pargs["apic"] = "true" + pargs["timeoffset"] = "0" + pargs["acpi"] = "1" + self.config.PlatformArgs = pargs + } + + // Validation + + if self.config.SourcePath == "" { + errs = packer.MultiErrorAppend(errs, fmt.Errorf("A source_path must be specified")) + } + + if len(errs.Errors) > 0 { + retErr = errors.New(errs.Error()) + } + + return nil, nil, retErr + +} + +func (self *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (packer.Artifact, error) { + //Setup XAPI client + c, err := xscommon.NewXenAPIClient(self.config.HostIp, self.config.Username, self.config.Password) + + if err != nil { + return nil, err + } + + ui.Say("XAPI client session established") + + c.GetClient().Host.GetAll(c.GetSessionRef()) + + //Share state between the other steps using a statebag + state := new(multistep.BasicStateBag) + state.Put("client", c) + // state.Put("config", self.config) + state.Put("commonconfig", self.config.CommonConfig) + state.Put("hook", hook) + state.Put("ui", ui) + + httpReqChan := make(chan string, 1) + + //Build the steps + steps := []multistep.Step{ + &xscommon.StepPrepareOutputDir{ + Force: self.config.PackerForce, + Path: self.config.OutputDir, + }, + &commonsteps.StepCreateFloppy{ + Files: self.config.FloppyFiles, + }, + new(xscommon.StepHTTPServer), + &xscommon.StepUploadVdi{ + VdiNameFunc: func() string { + return "Packer-floppy-disk" + }, + ImagePathFunc: func() string { + if floppyPath, ok := state.GetOk("floppy_path"); ok { + return floppyPath.(string) + } + return "" + }, + VdiUuidKey: "floppy_vdi_uuid", + }, + &xscommon.StepFindVdi{ + VdiName: self.config.ToolsIsoName, + VdiUuidKey: "tools_vdi_uuid", + }, + new(stepImportInstance), + &xscommon.StepAttachVdi{ + VdiUuidKey: "floppy_vdi_uuid", + VdiType: xsclient.VbdTypeFloppy, + }, + &xscommon.StepAttachVdi{ + VdiUuidKey: "tools_vdi_uuid", + VdiType: xsclient.VbdTypeCD, + }, + new(xscommon.StepStartVmPaused), + new(xscommon.StepSetVmHostSshAddress), + new(xscommon.StepBootWait), + &xscommon.StepTypeBootCommand{ + Ctx: *self.config.GetInterpContext(), + }, + &xscommon.StepWaitForIP{ + Chan: httpReqChan, + Timeout: 300 * time.Minute, /*self.config.InstallTimeout*/ // @todo change this + }, + &communicator.StepConnect{ + Config: &self.config.SSHConfig.Comm, + Host: xscommon.CommHost, + SSHConfig: xscommon.SSHConfigFunc(self.config.CommonConfig.SSHConfig), + SSHPort: xscommon.SSHPort, + }, + new(commonsteps.StepProvision), + new(xscommon.StepShutdown), + &xscommon.StepDetachVdi{ + VdiUuidKey: "floppy_vdi_uuid", + }, + &xscommon.StepDetachVdi{ + VdiUuidKey: "tools_vdi_uuid", + }, + new(xscommon.StepExport), + } + + self.runner = &multistep.BasicRunner{Steps: steps} + self.runner.Run(ctx, state) + + 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.") + } + + artifact, _ := xscommon.NewArtifact(self.config.OutputDir) + + return artifact, nil +} diff --git a/builder/xenserver/xva/builder_test.go b/builder/xenserver/xva/builder_test.go new file mode 100644 index 0000000..03e029f --- /dev/null +++ b/builder/xenserver/xva/builder_test.go @@ -0,0 +1,189 @@ +package xva + +import ( + "testing" + + "github.com/hashicorp/packer-plugin-sdk/packer" +) + +func testConfig() map[string]interface{} { + return map[string]interface{}{ + "remote_host": "localhost", + "remote_username": "admin", + "remote_password": "admin", + "vm_name": "foo", + "shutdown_command": "yes", + "ssh_username": "foo", + "source_path": ".", + + packer.BuildNameConfigKey: "foo", + } +} + +func TestBuilder_ImplementsBuilder(t *testing.T) { + var raw interface{} + raw = &Builder{} + if _, ok := raw.(packer.Builder); !ok { + t.Error("Builder must implement builder.") + } +} + +func TestBuilderPrepare_Defaults(t *testing.T) { + var b Builder + config := testConfig() + _, warns, err := b.Prepare(config) + if len(warns) > 0 { + t.Fatalf("bad: %#v", warns) + } + if err != nil { + t.Fatalf("should not have error: %s", err) + } + + if b.config.ToolsIsoName != "xs-tools.iso" { + t.Errorf("bad tools ISO name: %s", b.config.ToolsIsoName) + } + + if b.config.VMName == "" { + t.Errorf("bad vm name: %s", b.config.VMName) + } + + if b.config.Format != "xva" { + t.Errorf("bad format: %s", b.config.Format) + } + + if b.config.KeepVM != "never" { + t.Errorf("bad keep instance: %s", b.config.KeepVM) + } +} + +func TestBuilderPrepare_Format(t *testing.T) { + var b Builder + config := testConfig() + + // Bad + config["format"] = "foo" + _, warns, err := b.Prepare(config) + if len(warns) > 0 { + t.Fatalf("bad: %#v", warns) + } + if err == nil { + t.Fatal("should have error") + } + + // Good + config["format"] = "vdi_raw" + b = Builder{} + _, warns, err = b.Prepare(config) + if len(warns) > 0 { + t.Fatalf("bad: %#v", warns) + } + if err != nil { + t.Fatalf("should not have error: %s", err) + } +} + +func TestBuilderPrepare_HTTPPort(t *testing.T) { + var b Builder + config := testConfig() + + // Bad + config["http_port_min"] = 1000 + config["http_port_max"] = 500 + _, warns, err := b.Prepare(config) + if len(warns) > 0 { + t.Fatalf("bad: %#v", warns) + } + if err == nil { + t.Fatal("should have error") + } + + // Bad + config["http_port_min"] = -500 + b = Builder{} + _, warns, err = b.Prepare(config) + if len(warns) > 0 { + t.Fatalf("bad: %#v", warns) + } + if err == nil { + t.Fatal("should have error") + } + + // Good + config["http_port_min"] = 500 + config["http_port_max"] = 1000 + b = Builder{} + _, warns, err = b.Prepare(config) + if len(warns) > 0 { + t.Fatalf("bad: %#v", warns) + } + if err != nil { + t.Fatalf("should not have error: %s", err) + } +} + +func TestBuilderPrepare_InvalidKey(t *testing.T) { + var b Builder + config := testConfig() + + // Add a random key + config["i_should_not_be_valid"] = true + _, warns, err := b.Prepare(config) + if len(warns) > 0 { + t.Fatalf("bad: %#v", warns) + } + if err == nil { + t.Fatal("should have error") + } +} + +func TestBuilderPrepare_KeepVM(t *testing.T) { + var b Builder + config := testConfig() + + // Bad + config["keep_vm"] = "foo" + _, warns, err := b.Prepare(config) + if len(warns) > 0 { + t.Fatalf("bad: %#v", warns) + } + if err == nil { + t.Fatal("should have error") + } + + // Good + config["keep_vm"] = "always" + b = Builder{} + _, warns, err = b.Prepare(config) + if len(warns) > 0 { + t.Fatalf("bad: %#v", warns) + } + if err != nil { + t.Fatalf("should not have error: %s", err) + } +} + +func TestBuilderPrepare_SourcePath(t *testing.T) { + var b Builder + config := testConfig() + + // Bad + config["source_path"] = "" + _, warns, err := b.Prepare(config) + if len(warns) > 0 { + t.Fatalf("bad: %#v", warns) + } + if err == nil { + t.Fatal("should have error") + } + + // Good + config["source_path"] = "." + b = Builder{} + _, warns, err = b.Prepare(config) + if len(warns) > 0 { + t.Fatalf("bad: %#v", warns) + } + if err != nil { + t.Fatalf("should not have error: %s", err) + } +} diff --git a/builder/xenserver/xva/step_import_instance.go b/builder/xenserver/xva/step_import_instance.go new file mode 100644 index 0000000..54215fa --- /dev/null +++ b/builder/xenserver/xva/step_import_instance.go @@ -0,0 +1,114 @@ +package xva + +import ( + "context" + "fmt" + "os" + + "github.com/hashicorp/packer-plugin-sdk/multistep" + "github.com/hashicorp/packer-plugin-sdk/packer" + xsclient "github.com/terra-farm/go-xen-api-client" + xscommon "github.com/xenserver/packer-builder-xenserver/builder/xenserver/common" +) + +type stepImportInstance struct { + instance xsclient.VMRef + vdi xsclient.VDIRef +} + +func (self *stepImportInstance) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction { + + c := state.Get("client").(*xscommon.Connection) + config := state.Get("config").(xscommon.Config) + ui := state.Get("ui").(packer.Ui) + + ui.Say("Step: Import Instance") + + // find the SR + srs, err := c.GetClient().SR.GetAll(c.GetSessionRef()) + sr := srs[0] + if err != nil { + ui.Error(fmt.Sprintf("Unable to get SR: %s", err.Error())) + return multistep.ActionHalt + } + + // Open the file for reading (NB: httpUpload closes the file for us) + fh, err := os.Open(config.SourcePath) + if err != nil { + ui.Error(fmt.Sprintf("Unable to open XVA '%s': %s", config.SourcePath, err.Error())) + return multistep.ActionHalt + } + + result, err := xscommon.HTTPUpload(fmt.Sprintf("https://%s/import?session_id=%s&sr_id=%s", + c.Host, + c.GetSession(), + sr, + ), fh, state) + if err != nil { + ui.Error(fmt.Sprintf("Unable to upload VDI: %s", err.Error())) + return multistep.ActionHalt + } + if result == "" { + ui.Error("XAPI did not reply with an instance reference") + return multistep.ActionHalt + } + + instance := xsclient.VMRef(result) + + instanceId, err := c.GetClient().VM.GetUUID(c.GetSessionRef(), instance) + if err != nil { + ui.Error(fmt.Sprintf("Unable to get VM UUID: %s", err.Error())) + return multistep.ActionHalt + } + state.Put("instance_uuid", instanceId) + + err = c.GetClient().VM.SetVCPUsMax(c.GetSessionRef(), instance, int(config.VCPUsMax)) + if err != nil { + ui.Error(fmt.Sprintf("Error setting VM VCPUs Max=%d: %s", config.VCPUsMax, err.Error())) + return multistep.ActionHalt + } + + err = c.GetClient().VM.SetVCPUsAtStartup(c.GetSessionRef(), instance, int(config.VCPUsAtStartup)) + if err != nil { + ui.Error(fmt.Sprintf("Error setting VM VCPUs At Startup=%d: %s", config.VCPUsAtStartup, err.Error())) + return multistep.ActionHalt + } + + err = c.GetClient().VM.SetNameDescription(c.GetSessionRef(), instance, config.VMDescription) + if err != nil { + ui.Error(fmt.Sprintf("Error setting VM description: %s", err.Error())) + return multistep.ActionHalt + } + + ui.Say(fmt.Sprintf("Imported instance '%s'", instanceId)) + + return multistep.ActionContinue +} + +func (self *stepImportInstance) Cleanup(state multistep.StateBag) { + /* + config := state.Get("config").(config) + if config.ShouldKeepVM(state) { + return + } + + ui := state.Get("ui").(packer.Ui) + + if self.instance != nil { + ui.Say("Destroying VM") + _ = self.instance.HardShutdown() // redundant, just in case + err := self.instance.Destroy() + if err != nil { + ui.Error(err.Error()) + } + } + + if self.vdi != nil { + ui.Say("Destroying VDI") + err := self.vdi.Destroy() + if err != nil { + ui.Error(err.Error()) + } + } + */ +} diff --git a/debian-preseed.cfg b/debian-preseed.cfg new file mode 100644 index 0000000..134f5fe --- /dev/null +++ b/debian-preseed.cfg @@ -0,0 +1,106 @@ +# To see all available options execute this command once the install is done: +# sudo less /var/log/installer/cdebconf/questions.dat +# If you need information about an option use the command below (example for keymap): +# grep -A 4 "keyboard-configuration/xkb-keymap" /var/log/installer/cdebconf/templates.dat + +d-i debconf/priority select critical +# Use network mirror for package installation +# d-i apt-setup/use_mirror boolean true + +# Automatic installation +d-i auto-install/enable boolean true + +# "linux-server" is substituted by "linux-image-amd64" +# Possible options : "linux-image-amd64"(default) or "linux-image-rt-amd64" +d-i base-installer/kernel/override-image string linux-server + +# Configure hardware clock +d-i clock-setup/utc boolean true +d-i clock-setup/utc-auto boolean true + +# d-i console-setup/ask_detect boolean false + +# d-i debconf/frontend select noninteractive + +# Set OS locale +d-i debian-installer/language string en +d-i debian-installer/country string US +d-i debian-installer/locale string en_US.UTF-8 + +# d-i debian-installer/framebuffer boolean false + +# Reboot once the install is done +d-i finish-install/reboot_in_progress note + +# Bootloader options +d-i grub-installer/only_debian boolean true +d-i grub-installer/with_other_os boolean true +d-i grub-installer/bootdev string /dev/xvda + +# Set the keyboard layout +d-i keyboard-configuration/xkb-keymap select us + +# Mirror from which packages will be downloaded +d-i mirror/country string manual +d-i mirror/http/directory string /debian +d-i mirror/http/hostname string httpredir.debian.org + +# Configure http proxy if needed "http://[[user][:pass]@]host[:port]/" +d-i mirror/http/proxy string + +# Disk configuration +d-i partman-efi/non_efi_system boolean true +d-i partman-auto-lvm/guided_size string max +d-i partman-auto/choose_recipe select atomic +d-i partman-auto/method string lvm +d-i partman-lvm/confirm boolean true +d-i partman-lvm/confirm_nooverwrite boolean true +d-i partman-lvm/device_remove_lvm boolean true +d-i partman/choose_partition select finish +d-i partman/confirm boolean true +d-i partman/confirm_nooverwrite boolean true +d-i partman/confirm_write_new_label boolean true + +# User configuration +d-i passwd/root-login boolean true +d-i passwd/root-password-again password packer +d-i passwd/root-password password packer +d-i passwd/user-fullname string packer +d-i passwd/user-uid string 1000 +d-i passwd/user-password password packer +d-i passwd/user-password-again password packer +d-i passwd/username string packer + +# Extra packages to be installed +d-i pkgsel/include string sudo + +d-i pkgsel/install-language-support boolean false +d-i pkgsel/update-policy select none + +# Whether to upgrade packages after debootstrap +d-i pkgsel/upgrade select full-upgrade + +# Set timezone +d-i time/zone string Europe/Paris + +# Allow weak user password +d-i user-setup/allow-password-weak boolean true + +# Home folder encryption +d-i user-setup/encrypt-home boolean false + +# Do not scan additional CDs +apt-cdrom-setup apt-setup/cdrom/set-first boolean false + +# Use network mirror +apt-mirror-setup apt-setup/use_mirror boolean true + +# Disable polularity contest +popularity-contest popularity-contest/participate boolean false + +# Select base install +tasksel tasksel/first multiselect standard, ssh-server + +# Setup passwordless sudo for packer user +d-i preseed/late_command string \ + echo "packer ALL=(ALL:ALL) NOPASSWD:ALL" > /target/etc/sudoers.d/packer && chmod 0440 /target/etc/sudoers.d/packer \ No newline at end of file diff --git a/docs/builders/iso/xenserver-iso.html.markdown b/docs/builders/iso/xenserver-iso.html.markdown new file mode 100644 index 0000000..9c62c3f --- /dev/null +++ b/docs/builders/iso/xenserver-iso.html.markdown @@ -0,0 +1,255 @@ +--- +layout: "docs" +page_title: "XenServer Builder (from an ISO)" +description: |- + The XenServer Packer builder is able to create XenServer virtual machines and export them either as an XVA or a VDI and create VM templates starting from an ISO image. +--- + +# XenServer Builder (from an ISO) + +Type: `xenserver-iso` + +The XenServer Packer builder is able to create [XenServer](https://www.xenserver.org/) +virtual machines and export them either as an XVA or a VDI and create VM templates starting from an ISO image. + +The builder builds a virtual machine by creating a new virtual machine +from scratch, booting it, installing an OS, provisioning software within +the OS, then shutting it down. The result of the XenServer builder is a +directory containing all the files necessary to run the virtual machine +portably. + + +## Configuration Reference + +There are many configuration options available for the XenServer builder. +They are organized below into two categories: required and optional. Within +each category, the available options are alphabetized and described. + +### Required: + +* `iso_checksum` (string) - The checksum for the OS ISO file. Because ISO + files are so large, this is required and Packer will verify it prior + to booting a virtual machine with the ISO attached. The type of the + checksum is specified with `iso_checksum_type`, documented below. + +* `iso_checksum_type` (string) - The type of the checksum specified in + `iso_checksum`. Valid values are "none", "md5", "sha1", "sha256", or + "sha512" currently. While "none" will skip checksumming, this is not + recommended since ISO files are generally large and corruption does happen + from time to time. + +* `iso_url` (string) - A URL to the ISO containing the installation image. + This URL can be either an HTTP URL or a file URL (or path to a file). + If this is an HTTP URL, Packer will download it and cache it between + runs. + +* `remote_host` (string) - The host of the Xenserver / XCP-ng pool primary. Typically these will be specified through environment variables as seen in the [examples](../../examples/centos8.json). + +* `remote_username` (string) - The XenServer username used to access the remote machine. + +* `remote_password` (string) - The XenServer password for access to the remote machine. + +* `ssh_username` (string) - The username to use to SSH into the machine + once the OS is installed. + +### Optional: + +* `boot_command` (array of strings) - This is an array of commands to type + when the virtual machine is first booted. The goal of these commands should + be to type just enough to initialize the operating system installer. Special + keys can be typed as well, and are covered in the section below on the boot + command. If this is not specified, it is assumed the installer will start + itself. See the [Ubuntu](../../examples/ubuntu-2004.json) and [centos](../../examples/centos8.json) examples to see how these are used to launch autoinstall and kickstart respectively. + +* `boot_wait` (string) - The time to wait after booting the initial virtual + machine before typing the `boot_command`. The value of this should be + a duration. Examples are "5s" and "1m30s" which will cause Packer to wait + five seconds and one minute 30 seconds, respectively. If this isn't specified, + the default is 10 seconds. + +* `clone_template` (string) - The template to clone. Defaults to "Other install + media", this is "other", but you can get _dramatic_ performance improvements + by setting this to the proper value. To view all available values for this + run `xe template-list`. Setting the correct value hints to XenServer how to + optimize the virtual hardware to work best with that operating system. + +* `disk_size` (integer) - The size, in megabytes, of the hard disk to create + for the VM. By default, this is 40000 (about 40 GB). + +* `floppy_files` (array of strings) - A list of files to place onto a floppy + disk that is attached when the VM is booted. This is most useful + for unattended Windows installs, which look for an `Autounattend.xml` file + on removable media. By default, no floppy will be attached. All files + listed in this setting get placed into the root directory of the floppy + and the floppy is attached as the first floppy device. Currently, no + support exists for creating sub-directories on the floppy. Wildcard + characters (\*, ?, and []) are allowed. Directory names are also allowed, + which will add all the files found in the directory to the floppy. + +* `format` (string) - Either "xva", "vdi_raw" or "none", this specifies the + output format of the exported virtual machine. This defaults to "xva". Set to + "vdi_raw" to export just the raw disk image. Set to "none" to export nothing; + this is only useful with "keep_vm" set to "always" or "on_success". + +* `http_directory` (string) - Path to a directory to serve using an HTTP + server. The files in this directory will be available over HTTP that will + be requestable from the virtual machine. This is useful for hosting + kickstart files and so on. By default this is "", which means no HTTP + server will be started. The address and port of the HTTP server will be + available as variables in `boot_command`. This is covered in more detail + below. + +* `http_port_min` and `http_port_max` (integer) - These are the minimum and + maximum port to use for the HTTP server started to serve the `http_directory`. + Because Packer often runs in parallel, Packer will choose a randomly available + port in this range to run the HTTP server. If you want to force the HTTP + server to be on one port, make this minimum and maximum port the same. + By default the values are 8000 and 9000, respectively. + +* `install_timeout` (string) - The amount of time to wait after booting the VM + for the installer to shut itself down. + If it doesn't shut down in this time, it is an error. By default, the timeout + is "200m", or over three hours. + +* `iso_urls` (array of strings) - Multiple URLs for the ISO to download. + Packer will try these in order. If anything goes wrong attempting to download + or while downloading a single URL, it will move on to the next. All URLs + must point to the same file (same checksum). By default this is empty + and `iso_url` is used. Only one of `iso_url` or `iso_urls` can be specified. + +* `keep_vm` (string) - Determine when to keep the VM and when to clean it up. This + can be "always", "never" or "on_success". By default this is "never", and Packer + always deletes the VM regardless of whether the process succeeded and an artifact + was produced. "always" asks Packer to leave the VM at the end of the process + regardless of success. "on_success" requests that the VM only be cleaned up if an + artifact was produced. The latter is useful for debugging templates that fail. + +* `network_names` (array of strings) - A list of networks identified by their name label which + will be used for the VM during creation. The first network will correspond to the VM's + first network interface (VIF), the second will corespond to the second VIF and so on. + +* `output_directory` (string) - 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. + +* `platform_args` (object of key/value strings) - The platform args. + Defaults to +```javascript +{ + "viridian": "false", + "nx": "true", + "pae": "true", + "apic": "true", + "timeoffset": "0", + "acpi": "1", + "cores-per-socket": "1" +} +``` + +* `shutdown_command` (string) - The command to use to gracefully shut down + the machine once all the provisioning is done. If this is omitted, packer + will shut down the VM gracefully through the Xen api's vm shutdown command. Unless + you have special requirements this should typically be left to its default. + +* `ssh_host_port_min` and `ssh_host_port_max` (integer) - The minimum and + maximum port to use for the SSH port on the host machine which is forwarded + to the SSH 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. + +* `ssh_key_path` (string) - Path to a private key to use for authenticating + with SSH. By default this is not set (key-based auth won't be used). + The associated public key is expected to already be configured on the + VM being prepared by some other process (kickstart, etc.). + +* `ssh_password` (string) - The password for `ssh_username` to use to + authenticate with SSH. By default this is the empty string. + +* `ssh_port` (integer) - The port that SSH will be listening on in the guest + virtual machine. By default this is 22. + +* `ssh_wait_timeout` (string) - The duration to wait for SSH to become + available. By default this is "20m", or 20 minutes. Note that this should + be quite long since the timer begins as soon as the virtual machine is booted. + +* `tools_iso_name` (string) - The name of the XenServer Tools ISO. Defaults to + "xs-tools.iso". + +* `vm_description` (string) - The description of the new virtual + machine. By default this is the empty string. + +* `vm_name` (string) - This is the name of the new virtual + machine, without the file extension. By default this is + "packer-BUILDNAME-TIMESTAMP", where "BUILDNAME" is the name of the build. + +* `vcpus_max` (integer) - The maximum number of VCPUs for the VM. + By default this is 1. + +* `vcpus_atstartup` (integer) - The number of startup VCPUs for the VM. + By default this is 1. + +* `vm_memory` (integer) - The size, in megabytes, of the amount of memory to + allocate for the VM. By default, this is 1024 (1 GB). + +## Differences with other Packer builders + +Currently the XenServer builder has some quirks when compared with other Packer builders. + +The builder currently only works remotely. + +The installer is expected to shut down the VM to indicate that it has completed. This is in contrast to other builders, which instead detect completion by a successful SSH connection. The reason for this difference is that currently the builder has no way of knowing what the IP address of the VM is without starting it on the HIMN. + +## Boot Command + +The `boot_command` configuration is very important: it specifies the keys +to type when the virtual machine is first booted in order to start the +OS installer. This command is typed after `boot_wait`, which gives the +virtual machine some time to actually load the ISO. + +As documented above, the `boot_command` is an array of strings. The +strings are all typed in sequence. It is an array only to improve readability +within the template. + +The boot command is "typed" character for character over a VNC connection +to the machine, simulating a human actually typing the keyboard. There are +a set of special keys available. If these are in your boot command, they +will be replaced by the proper key: + +* `` - Backspace + +* `` - Delete + +* `` and `` - Simulates an actual "enter" or "return" keypress. + +* `` - Simulates pressing the escape key. + +* `` - Simulates pressing the tab key. + +* `` - `` - Simulates pressing a function key. + +* `` `` `` `` - Simulates pressing an arrow key. + +* `` - Simulates pressing the spacebar. + +* `` - Simulates pressing the insert key. + +* `` `` - Simulates pressing the home and end keys. + +* `` `` - Simulates pressing the page up and page down keys. + +* `` `` `` - Adds a 1, 5 or 10 second pause before sending any additional keys. This + is useful if you have to generally wait for the UI to update before typing more. + +In addition to the special keys, each command to type is treated as a +configuration template. +The available variables are: + +* `HTTPIP` and `HTTPPort` - The IP and port, respectively of an HTTP server + that is started serving the directory specified by the `http_directory` + configuration parameter. If `http_directory` isn't specified, these will be + blank! + +See the [examples](../../examples/) for working boot commands. diff --git a/examples/README.md b/examples/README.md new file mode 100644 index 0000000..ede7545 --- /dev/null +++ b/examples/README.md @@ -0,0 +1,36 @@ +## Examples + +In order for new users to get up and running with the packer builder, a few examples of building a machine image with popular distros have been created. + +In order to see an exhaustive list of configuration options for the packer builder please see the [following documentation](../docs/builders/xenserver-iso.html.markdown). This doc will focus on the details relevant to the particular distro. + +### Running the examples + +In order to run the examples you will need to perform the following steps: +1. Export those vars: +``` +PKR_VAR_remote_host +PKR_VAR_remote_password +PKR_VAR_remote_username +PKR_VAR_sr_name +PKR_VAR_sr_iso_name +``` +`PKR_VAR_remote_host` must be the resource pool primary, aka the master. + +2. Run `packer init path/to/defenition.pkr.hcl` to download the xenserver plugin + +2. Run `packer build path/to/defenition.pkr.hcl` +so for example: +`packer build examples/centos/centos8-netinstall.pkr.hcl` + +### Ubuntu + +The Ubuntu example uses the [autoinstall tool](https://ubuntu.com/server/docs/install/autoinstallhttps://ubuntu.com/server/docs/install/autoinstall) to configure the VM template. Please see the [autoinstall docs](https://ubuntu.com/server/docs/install/autoinstall-reference) for an exhaustive list of what is supported. + +Packer will create a http server to serve the files as specified from the `http_directory` specified in the builder configuration. This is where the [user-data](http/ubuntu-2004/user-data) and [meta-data](http/ubuntu-2004/meta-data) for autoinstall must be present. + +### Centos + +The Centos examples use kickstart files to configure the VM template. Please see the [kickstart documentation](https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/7/html/installation_guide/sect-kickstart-syntax) for the options that are supported. + +Packer will create a http server to serve the files as specified from the `http_directory` specified in the builder configuration. This is where the [kickstart config](http/centos8/ks-centos8.cfg) file must be present. diff --git a/examples/centos/centos8-local.pkr.hcl b/examples/centos/centos8-local.pkr.hcl new file mode 100644 index 0000000..c21eb8f --- /dev/null +++ b/examples/centos/centos8-local.pkr.hcl @@ -0,0 +1,81 @@ +packer { + required_plugins { + xenserver= { + version = ">= v0.3.2" + source = "github.com/ddelnano/xenserver" + } + } +} + +variable "remote_host" { + type = string + description = "The ip or fqdn of your XenServer. This will be pulled from the env var 'PKR_VAR_remote_host'" + sensitive = true + default = null +} + +variable "remote_password" { + type = string + description = "The password used to interact with your XenServer. This will be pulled from the env var 'PKR_VAR_remote_password'" + sensitive = true + default = null +} + +variable "remote_username" { + type = string + description = "The username used to interact with your XenServer. This will be pulled from the env var 'PKR_VAR_remote_username'" + sensitive = true + default = null + +} + +variable "sr_iso_name" { + type = string + default = "" + description = "The ISO-SR to packer will use" + +} + +variable "sr_name" { + type = string + default = "" + description = "The name of the SR to packer will use" +} + +locals { + timestamp = regex_replace(timestamp(), "[- TZ:]", "") +} + +source "xenserver-iso" "centos8-local" { + iso_checksum = "aaf9d4b3071c16dbbda01dfe06085e5d0fdac76df323e3bbe87cce4318052247" + iso_checksum_type = "sha1" + iso_url = "http://mirrors.ocf.berkeley.edu/centos/8.3.2011/isos/x86_64/CentOS-8.3.2011-x86_64-dvd1.iso" + + sr_iso_name = var.sr_iso_name + sr_name = var.sr_name + tools_iso_name = "guest-tools.iso" + + remote_host = var.remote_host + remote_password = var.remote_password + remote_username = var.remote_username + + vm_name = "packer-centos8-local-${local.timestamp}" + vm_description = "Build started: ${local.timestamp}\n This was installed from the dvd" + vm_memory = 4096 + disk_size = 4096 + + http_directory = "examples/http/centos8" + boot_command = [" text ks=http://{{ .HTTPIP }}:{{ .HTTPPort }}/ks-centos8-local.cfg"] + boot_wait = "10s" + + ssh_username = "root" + ssh_password = "centos" + ssh_wait_timeout = "10000s" + + output_directory = "packer-centos8-local" + keep_vm = "always" +} + +build { + sources = ["xenserver-iso.centos8-local"] +} diff --git a/examples/centos/centos8-netinstall.pkr.hcl b/examples/centos/centos8-netinstall.pkr.hcl new file mode 100644 index 0000000..e34d369 --- /dev/null +++ b/examples/centos/centos8-netinstall.pkr.hcl @@ -0,0 +1,81 @@ +packer { + required_plugins { + xenserver= { + version = ">= v0.3.2" + source = "github.com/ddelnano/xenserver" + } + } +} + +variable "remote_host" { + type = string + description = "The ip or fqdn of your XenServer. This will be pulled from the env var 'PKR_VAR_remote_host'" + sensitive = true + default = null +} + +variable "remote_password" { + type = string + description = "The password used to interact with your XenServer. This will be pulled from the env var 'PKR_VAR_remote_password'" + sensitive = true + default = null +} + +variable "remote_username" { + type = string + description = "The username used to interact with your XenServer. This will be pulled from the env var 'PKR_VAR_remote_username'" + sensitive = true + default = null + +} + +variable "sr_iso_name" { + type = string + default = "" + description = "The ISO-SR to packer will use" + +} + +variable "sr_name" { + type = string + default = "" + description = "The name of the SR to packer will use" +} + +locals { + timestamp = regex_replace(timestamp(), "[- TZ:]", "") +} + +source "xenserver-iso" "centos8-netinstall" { + iso_checksum = "07a8e59c42cc086ec4c49bdce4fae5a17b077dea" + iso_checksum_type = "sha1" + iso_url = "http://mirrors.ocf.berkeley.edu/centos/8.3.2011/isos/x86_64/CentOS-8.3.2011-x86_64-boot.iso" + + sr_iso_name = var.sr_iso_name + sr_name = var.sr_name + tools_iso_name = "guest-tools.iso" + + remote_host = var.remote_host + remote_password = var.remote_password + remote_username = var.remote_username + + vm_name = "packer-centos8-netinstall-${local.timestamp}" + vm_description = "Build started: ${local.timestamp}\n This was installed with an external repository" + vm_memory = 4096 + disk_size = 4096 + + http_directory = "examples/http/centos8" + boot_command = [" text ks=http://{{ .HTTPIP }}:{{ .HTTPPort }}/ks-centos8-netinstall.cfg"] + boot_wait = "10s" + + ssh_username = "root" + ssh_password = "centos" + ssh_wait_timeout = "10000s" + + output_directory = "packer-centos8-netinstall" + keep_vm = "always" +} + +build { + sources = ["xenserver-iso.centos8-netinstall"] +} diff --git a/examples/http/centos8/ks-centos8-local.cfg b/examples/http/centos8/ks-centos8-local.cfg new file mode 100644 index 0000000..a275dea --- /dev/null +++ b/examples/http/centos8/ks-centos8-local.cfg @@ -0,0 +1,47 @@ +eula --agreed +lang en-US.UTF-8 +keyboard --vckeymap='de' --xlayouts='de' +timezone Europe/Berlin + +cdrom + +text +skipx +firstboot --disable + +rootpw --plaintext centos + +firewall --enabled --ssh +selinux --enforcing + +# Installation logging level +logging --level=info + +network --bootproto=dhcp --device=eth0 --onboot=on + +# System bootloader configuration +bootloader --location=mbr +zerombr +clearpart --all + +# Disk partitioning information +part / --asprimary --fstype="ext4" --size=1024 --grow + + +%addon com_redhat_kdump --disable +%end + +%packages --ignoremissing --excludedocs +openssh-clients +sudo + +# unnecessary firmware +-aic94xx-firmware* +-alsa-* +-ivtv-* +-iwl*firmware +%end + +# Reboot after installation +reboot --eject + diff --git a/examples/http/centos8/ks-centos8-netinstall.cfg b/examples/http/centos8/ks-centos8-netinstall.cfg new file mode 100644 index 0000000..c9e7dc7 --- /dev/null +++ b/examples/http/centos8/ks-centos8-netinstall.cfg @@ -0,0 +1,46 @@ +eula --agreed +lang en-US.UTF-8 +keyboard --vckeymap='de' --xlayouts='de' +timezone Europe/Berlin + +text +skipx +firstboot --disable + +url --url="http://mirror.centos.org/centos/8.3.2011/BaseOS/x86_64/os/" +rootpw --plaintext centos + +firewall --enabled --ssh +selinux --enforcing + +# Installation logging level +logging --level=info + +network --bootproto=dhcp --device=eth0 --onboot=on + +# System bootloader configuration +bootloader --location=mbr +zerombr +clearpart --all + +# Disk partitioning information +part / --asprimary --fstype="ext4" --size=1024 --grow + + +%addon com_redhat_kdump --disable +%end + +%packages --ignoremissing --excludedocs +openssh-clients +sudo + +# unnecessary firmware +-aic94xx-firmware* +-alsa-* +-ivtv-* +-iwl*firmware +%end + +# Reboot after installation +reboot --eject + diff --git a/examples/http/ubuntu-2004/meta-data b/examples/http/ubuntu-2004/meta-data new file mode 100644 index 0000000..e69de29 diff --git a/examples/http/ubuntu-2004/user-data b/examples/http/ubuntu-2004/user-data new file mode 100644 index 0000000..9653b71 --- /dev/null +++ b/examples/http/ubuntu-2004/user-data @@ -0,0 +1,22 @@ +#cloud-config + +# hack for cloud-init per: +# https://github.com/leakespeake/packer/blob/3f3e361751b4be9326b66771d96f2519bc8f885e/builders/vmware/vsphere-iso/ubuntu-server-20-04/hcl2/http/ubuntu-server-subiquity/user-data +runcmd: + # to enable true auto-install for Ubuntu 20.04 with cloud-init nocloud (eliminates "Continue with autoinstall?" prompt) + - [eval, 'echo $(cat /proc/cmdline) "autoinstall" > /root/cmdline'] + - [eval, 'mount -n --bind -o ro /root/cmdline /proc/cmdline'] + - [eval, 'snap restart subiquity.subiquity-service'] + +autoinstall: + version: 1 + identity: + hostname: ubuntu-server + # This is the crypted pass of 'ubuntu' + password: "$6$exDY1mhS4KUYCE/2$zmn9ToZwTKLhCw.b4/b.ZRTIZM30JZ4QrOQ2aOXJ8yk96xpcCof0kxKwuX1kqLG/ygbJ1f8wxED22bTL4F46P0" + username: testuser + packages: + - xe-guest-utilities + ssh: + install-server: yes + allow-pw: yes diff --git a/examples/ubuntu/ubuntu-2004.pkr.hcl b/examples/ubuntu/ubuntu-2004.pkr.hcl new file mode 100644 index 0000000..67238ff --- /dev/null +++ b/examples/ubuntu/ubuntu-2004.pkr.hcl @@ -0,0 +1,86 @@ +packer { + required_plugins { + xenserver= { + version = ">= v0.3.2" + source = "github.com/ddelnano/xenserver" + } + } +} + +variable "remote_host" { + type = string + description = "The ip or fqdn of your XenServer. This will be pulled from the env var 'PKR_VAR_XAPI_HOST'" + sensitive = true + default = null +} + +variable "remote_password" { + type = string + description = "The password used to interact with your XenServer. This will be pulled from the env var 'PKR_VAR_XAPI_PASSWORD'" + sensitive = true + default = null +} + +variable "remote_username" { + type = string + description = "The username used to interact with your XenServer. This will be pulled from the env var 'PKR_VAR_XAPI_USERNAME'" + sensitive = true + default = null + +} + +variable "sr_iso_name" { + type = string + default = "" + description = "The ISO-SR to packer will use" + +} + +variable "sr_name" { + type = string + default = "" + description = "The name of the SR to packer will use" +} + +locals { + timestamp = regex_replace(timestamp(), "[- TZ:]", "") +} + + +source "xenserver-iso" "ubuntu-2004" { + iso_checksum = "5035be37a7e9abbdc09f0d257f3e33416c1a0fb322ba860d42d74aa75c3468d4" + iso_checksum_type = "sha256" + iso_url = "http://releases.ubuntu.com/20.04/ubuntu-20.04.5-live-server-amd64.iso" + + sr_iso_name = var.sr_iso_name + sr_name = var.sr_name + tools_iso_name = "guest-tools.iso" + + remote_host = var.remote_host + remote_password = var.remote_password + remote_username = var.remote_username + + # Change this to match the ISO of ubuntu you are using in the iso_url variable + clone_template = "Ubuntu Focal Fossa 20.04" + vm_name = "packer-ubuntu-2004-${local.timestamp}" + vm_description = "Build started: ${local.timestamp}" + vm_memory = 4096 + disk_size = 20000 + + floppy_files = [ + "examples/http/ubuntu-2004/meta-data", + "examples/http/ubuntu-2004/user-data", + ] + + ssh_username = "testuser" + ssh_password = "ubuntu" + ssh_wait_timeout = "60000s" + ssh_handshake_attempts = 10000 + + output_directory = "packer-ubuntu-2004-iso" + keep_vm = "always" +} + +build { + sources = ["xenserver-iso.ubuntu-2004"] +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..84364a8 --- /dev/null +++ b/go.mod @@ -0,0 +1,16 @@ +module github.com/xenserver/packer-builder-xenserver + +go 1.16 + +require ( + github.com/amfranz/go-xmlrpc-client v0.0.0-20190612172737-76858463955d + github.com/hashicorp/hcl/v2 v2.12.0 + github.com/hashicorp/packer-plugin-sdk v0.3.0 + github.com/mitchellh/go-vnc v0.0.0-20150629162542-723ed9867aed + github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect + github.com/stretchr/objx v0.2.0 // indirect + github.com/terra-farm/go-xen-api-client v0.0.2 + github.com/zclconf/go-cty v1.10.0 + golang.org/x/crypto v0.0.0-20220427172511-eb4f295cb31f + gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..c13b9b5 --- /dev/null +++ b/go.sum @@ -0,0 +1,1026 @@ +bazil.org/fuse v0.0.0-20160811212531-371fbbdaa898/go.mod h1:Xbm+BRKSBEpa4q4hTSxohYNQpsxXPbPry4JJWOB3LB8= +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= +cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= +cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= +cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= +cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= +cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= +cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= +cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= +cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= +cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= +cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= +cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= +cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= +cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI= +cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk= +cloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg= +cloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb8= +cloud.google.com/go v0.81.0/go.mod h1:mk/AM35KwGk/Nm2YSeZbxXdrNK3KZOYHmLkOqC2V6E0= +cloud.google.com/go v0.83.0/go.mod h1:Z7MJUsANfY0pYPdw0lbnivPx4/vhy/e2FEkSkF7vAVY= +cloud.google.com/go v0.84.0/go.mod h1:RazrYuxIK6Kb7YrzzhPoLmCVzl7Sup4NrbKPg8KHSUM= +cloud.google.com/go v0.87.0/go.mod h1:TpDYlFy7vuLzZMMZ+B6iRiELaY7z/gJPaqbMx6mlWcY= +cloud.google.com/go v0.90.0/go.mod h1:kRX0mNRHe0e2rC6oNakvwQqzyDmg57xJ+SZU1eT2aDQ= +cloud.google.com/go v0.93.3/go.mod h1:8utlLll2EF5XMAV15woO4lSbWQlk8rer9aLOfLh7+YI= +cloud.google.com/go v0.94.0 h1:QDB2MZHqjTt0hGKnoEWyG/iWykue/lvkLdogLgrg10U= +cloud.google.com/go v0.94.0/go.mod h1:qAlAugsXlC+JWO+Bke5vCtc9ONxjQT3drlTTnAplMW4= +cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= +cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= +cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= +cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= +cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= +cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= +cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= +cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= +cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= +cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= +cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= +cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= +cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= +cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= +cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= +cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= +cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= +cloud.google.com/go/storage v1.16.1 h1:sMEIc4wxvoY3NXG7Rn9iP7jb/2buJgWR1vNXCR/UPfs= +cloud.google.com/go/storage v1.16.1/go.mod h1:LaNorbty3ehnU3rEjXSNV/NRgQA0O8Y+uh6bPe5UOk4= +dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= +github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8= +github.com/Azure/go-ntlmssp v0.0.0-20180810175552-4a21cbd618b4/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU= +github.com/Azure/go-ntlmssp v0.0.0-20200615164410-66371956d46c h1:/IBSNwUN8+eKzUzbJPqhK839ygXJ82sde8x3ogr6R28= +github.com/Azure/go-ntlmssp v0.0.0-20200615164410-66371956d46c/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/ChrisTrenkamp/goxpath v0.0.0-20170922090931-c385f95c6022/go.mod h1:nuWgzSkT5PnyOd+272uUmV0dnAnAn42Mk7PiQC5VzN4= +github.com/ChrisTrenkamp/goxpath v0.0.0-20210404020558-97928f7e12b6 h1:w0E0fgc1YafGEh5cROhlROMWXiNoZqApk2PDN0M1+Ns= +github.com/ChrisTrenkamp/goxpath v0.0.0-20210404020558-97928f7e12b6/go.mod h1:nuWgzSkT5PnyOd+272uUmV0dnAnAn42Mk7PiQC5VzN4= +github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= +github.com/Masterminds/goutils v1.1.0/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= +github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= +github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= +github.com/Masterminds/sprig v2.22.0+incompatible/go.mod h1:y6hNFY5UBTIWBxnzTeuNhlNS5hqE0NB0E6fgfo2Br3o= +github.com/Microsoft/go-winio v0.4.15-0.20190919025122-fc70bd9a86b5/go.mod h1:tTuCMEN+UleMWgg9dVx4Hu52b1bJo+59jBh3ajtinzw= +github.com/Microsoft/hcsshim v0.8.9/go.mod h1:5692vkUqntj1idxauYlpoINNKeqCiG6Sg38RRsjT5y8= +github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= +github.com/agext/levenshtein v1.2.1/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558= +github.com/agext/levenshtein v1.2.3 h1:YB2fHEn0UJagG8T1rrWknE3ZQzWM06O8AMAatNn7lmo= +github.com/agext/levenshtein v1.2.3/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558= +github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/amfranz/go-xmlrpc-client v0.0.0-20190612172737-76858463955d h1:39lR6Kg+GsvDpLMD2Mb7gkjXmmLexqfr7SPy4iQWDTE= +github.com/amfranz/go-xmlrpc-client v0.0.0-20190612172737-76858463955d/go.mod h1:2NlXXRCkTbr/vZtUjcHKhbrESE4a3CDqVrgOROB16dg= +github.com/antchfx/xpath v1.1.11 h1:WOFtK8TVAjLm3lbgqeP0arlHpvCEeTANeWZ/csPpJkQ= +github.com/antchfx/xpath v1.1.11/go.mod h1:i54GszH55fYfBmoZXapTHN8T8tkcHfRgLyVwwqzXNcs= +github.com/antchfx/xquery v0.0.0-20180515051857-ad5b8c7a47b0 h1:JaCC8jz0zdMLk2m+qCCVLLLM/PL93p84w4pK3aJWj60= +github.com/antchfx/xquery v0.0.0-20180515051857-ad5b8c7a47b0/go.mod h1:LzD22aAzDP8/dyiCKFp31He4m2GPjl0AFyzDtZzUu9M= +github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= +github.com/apparentlymart/go-dump v0.0.0-20180507223929-23540a00eaa3 h1:ZSTrOEhiM5J5RFxEaFvMZVEAM1KvT1YzbEOwB2EAGjA= +github.com/apparentlymart/go-dump v0.0.0-20180507223929-23540a00eaa3/go.mod h1:oL81AME2rN47vu18xqj1S1jPIPuN7afo62yKTNn3XMM= +github.com/apparentlymart/go-textseg v1.0.0 h1:rRmlIsPEEhUTIKQb7T++Nz/A5Q6C9IuX2wFoYVvnCs0= +github.com/apparentlymart/go-textseg v1.0.0/go.mod h1:z96Txxhf3xSFMPmb5X/1W05FF/Nj9VFpLOpjS5yuumk= +github.com/apparentlymart/go-textseg/v13 v13.0.0 h1:Y+KvPE1NYz0xl601PVImeQfFyEy6iT90AvPUL1NNfNw= +github.com/apparentlymart/go-textseg/v13 v13.0.0/go.mod h1:ZK2fH7c4NqDTLtiYLvIkEghdlcqw7yxLeM89kiTRPUo= +github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= +github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= +github.com/armon/go-metrics v0.3.0/go.mod h1:zXjbSimjXTd7vOpY8B0/2LpvNvDoXBuplAD+gJD3GYs= +github.com/armon/go-metrics v0.3.3/go.mod h1:4O98XIr/9W0sxpJ8UaYkvjk10Iff7SnFrb4QAOwNTFc= +github.com/armon/go-metrics v0.3.9 h1:O2sNqxBdvq8Eq5xmzljcYzAORli6RWCvEym4cJf9m18= +github.com/armon/go-metrics v0.3.9/go.mod h1:4O98XIr/9W0sxpJ8UaYkvjk10Iff7SnFrb4QAOwNTFc= +github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= +github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= +github.com/aws/aws-sdk-go v1.25.37/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= +github.com/aws/aws-sdk-go v1.30.8/go.mod h1:5zCpMtNQVjRREroY7sYe8lOMRSxkhG6MZveU8YkpAk0= +github.com/aws/aws-sdk-go v1.30.27/go.mod h1:5zCpMtNQVjRREroY7sYe8lOMRSxkhG6MZveU8YkpAk0= +github.com/aws/aws-sdk-go v1.40.34 h1:SBYmodndE2d4AYucuuJnOXk4MD1SFbucoIdpwKVKeSA= +github.com/aws/aws-sdk-go v1.40.34/go.mod h1:585smgzpB/KqRA+K3y/NL/oYRqQvpNJYvLm+LY1U59Q= +github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= +github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= +github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d h1:xDfNPAt8lFiC1UJrqV3uuy861HCTo708pDMbjHHdCas= +github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d/go.mod h1:6QX/PXZ00z/TKoufEY6K/a0k6AhaJrQKdFe6OfVXsa4= +github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= +github.com/cenkalti/backoff/v3 v3.0.0/go.mod h1:cIeZDE3IrqwwJl6VUwCN6trj1oXrTS4rc0ij+ULvLYs= +github.com/cenkalti/backoff/v3 v3.2.2 h1:cfUAAO3yvKMYKPrvhDuHSwQnhZNk/RMHKdZqKTxfm6M= +github.com/cenkalti/backoff/v3 v3.2.2/go.mod h1:cIeZDE3IrqwwJl6VUwCN6trj1oXrTS4rc0ij+ULvLYs= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= +github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag= +github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/containerd/cgroups v0.0.0-20190919134610-bf292b21730f/go.mod h1:OApqhQ4XNSNC13gXIwDjhOQxjWa/NxkwZXJ1EvqT0ko= +github.com/containerd/console v0.0.0-20180822173158-c12b1e7919c1/go.mod h1:Tj/on1eG8kiEhd0+fhSDzsPAFESxzBBvdyEgyryXffw= +github.com/containerd/containerd v1.3.2/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= +github.com/containerd/containerd v1.3.4/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= +github.com/containerd/continuity v0.0.0-20190426062206-aaeac12a7ffc/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y= +github.com/containerd/continuity v0.0.0-20200709052629-daa8e1ccc0bc/go.mod h1:cECdGN1O8G9bgKTlLhuPJimka6Xb/Gg7vYzCTNVxhvo= +github.com/containerd/fifo v0.0.0-20190226154929-a9fb20d87448/go.mod h1:ODA38xgv3Kuk8dQz2ZQXpnv/UZZUHUCL7pnLehbXgQI= +github.com/containerd/go-runc v0.0.0-20180907222934-5a6d9f37cfa3/go.mod h1:IV7qH3hrUgRmyYrtgEeGWJfWbgcHL9CSRruz2Vqcph0= +github.com/containerd/ttrpc v0.0.0-20190828154514-0e0f228740de/go.mod h1:PvCDdDGpgqzQIzDW1TphrGLssLDZp2GuS+X5DkEJB8o= +github.com/containerd/typeurl v0.0.0-20180627222232-a93fcdb778cd/go.mod h1:Cm3kwCdlkCfMSHURc+r6fwoGH6/F1hH3S4sg0rLFWPc= +github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= +github.com/docker/docker v1.4.2-0.20200319182547-c7ad2b866182/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= +github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= +github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= +github.com/dylanmei/iso8601 v0.1.0 h1:812NGQDBcqquTfH5Yeo7lwR0nzx/cKdsmf3qMjPURUI= +github.com/dylanmei/iso8601 v0.1.0/go.mod h1:w9KhXSgIyROl1DefbMYIE7UVSIvELTbMrCfx+QkYnoQ= +github.com/dylanmei/winrmtest v0.0.0-20170819153634-c2fbb09e6c08 h1:0bp6/GrNOrTDtSXe9YYGCwf8jp5Fb/b+4a6MTRm4qzY= +github.com/dylanmei/winrmtest v0.0.0-20170819153634-c2fbb09e6c08/go.mod h1:VBVDFSBXCIW8JaHQpI8lldSKfYaLMzP9oyq6IJ4fhzY= +github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= +github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= +github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= +github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/fatih/camelcase v1.0.0/go.mod h1:yN2Sb0lFhZJUdVvtELVWefmrXpuZESvPmqwoZc+/fpc= +github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= +github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= +github.com/fatih/color v1.12.0 h1:mRhaKNwANqRgUBGKmnI5ZxEk7QXmjQeCcuYFMX2bfcc= +github.com/fatih/color v1.12.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM= +github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= +github.com/fatih/structtag v1.2.0/go.mod h1:mBJUNpUnHmRKrKlQQlmCrh5PuhftFbNv8Ys4/aAZl94= +github.com/frankban/quicktest v1.10.0 h1:Gfh+GAJZOAoKZsIZeZbdn2JF10kN1XHNvjsvQK8gVkE= +github.com/frankban/quicktest v1.10.0/go.mod h1:ui7WezCLWMWxVWr1GETZY3smRy0G4KWq9vcPtJmFl7Y= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/go-asn1-ber/asn1-ber v1.3.1/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0= +github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-ldap/ldap/v3 v3.1.3/go.mod h1:3rbOH3jRS2u6jg2rJnKAMLE/xQyCKIveG2Sa/Cohzb8= +github.com/go-ldap/ldap/v3 v3.1.10/go.mod h1:5Zun81jBTabRaI8lzN7E1JjyEl1g6zI6u9pd8luAK4Q= +github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= +github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= +github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= +github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/go-test/deep v1.0.2-0.20181118220953-042da051cf31/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA= +github.com/go-test/deep v1.0.2/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA= +github.com/go-test/deep v1.0.3 h1:ZrJSEWsXzPOxaZnFteGEfooLba+ju3FYIbOrS+rQd68= +github.com/go-test/deep v1.0.3/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA= +github.com/godbus/dbus v0.0.0-20190422162347-ade71ed3457e/go.mod h1:bBOAhwG1umN6/6ZUMtDFBMQR8jRg9O75tm9K00oMsK4= +github.com/gofrs/flock v0.8.1 h1:+gYjHKf32LDeiEEFhQaotPbLuUXjY5ZqxKgXy7n59aw= +github.com/gofrs/flock v0.8.1/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU= +github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= +github.com/gofrs/uuid v4.0.0+incompatible h1:1SD/1F5pU8p29ybwgQSwpQk+mwdRrXCYuPhW6m+TnJw= +github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= +github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= +github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= +github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= +github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8= +github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= +github.com/golang/protobuf v1.1.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM= +github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= +github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= +github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/btree v1.0.0 h1:0udJVsspx3VBr5FwtLhQQtuAsVc79tTq0ocGIPAU6qo= +github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ= +github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/martian v2.1.0+incompatible h1:/CP5g8u/VJHijgedC/Legn3BAbAaWPgecwXBIDzw5no= +github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= +github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= +github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= +github.com/google/martian/v3 v3.2.1 h1:d8MncMlErDFTwQGBK1xhv026j9kqhvw1Qv9IbWT1VLQ= +github.com/google/martian/v3 v3.2.1/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk= +github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210601050228-01bbb1931b22/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210609004039-a478d1d731e9/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= +github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= +github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= +github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= +github.com/googleapis/gax-go/v2 v2.1.0 h1:6DWmvNpomjL1+3liNSZbVns3zsYzzCjm6pRBO1tLeso= +github.com/googleapis/gax-go/v2 v2.1.0/go.mod h1:Q3nei7sK6ybPYH7twZdmQpAd1MKb7pfu6SK+H1/DsU0= +github.com/gorilla/mux v1.7.4/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= +github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= +github.com/hashicorp/consul/api v1.10.1 h1:MwZJp86nlnL+6+W1Zly4JUuVn9YHhMggBirMpHGD7kw= +github.com/hashicorp/consul/api v1.10.1/go.mod h1:XjsvQN+RJGWI2TWy1/kqaE16HrR2J/FWgkYjdZQsX9M= +github.com/hashicorp/consul/sdk v0.8.0 h1:OJtKBtEjboEZvG6AOUdh4Z1Zbyu0WcxQ0qatRrZHTVU= +github.com/hashicorp/consul/sdk v0.8.0/go.mod h1:GBvyrGALthsZObzUGsfgHZQDXjg4lOjagTIwIR1vPms= +github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= +github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= +github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= +github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ= +github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= +github.com/hashicorp/go-getter/gcs/v2 v2.1.0 h1:1S1hvWgHrhUihP/Y4FVbjCWwE7EwxpksKoRcC7g+Hgs= +github.com/hashicorp/go-getter/gcs/v2 v2.1.0/go.mod h1:dVyTnX1BynHAjbumB4Pk14GoJ+v3VbDUJtbI7G0oOlU= +github.com/hashicorp/go-getter/s3/v2 v2.1.0 h1:8uwuP97zEQ7y7H4bLzRqiN4T8vmpXeJthigqSEjX+08= +github.com/hashicorp/go-getter/s3/v2 v2.1.0/go.mod h1:rwzJPQaBuc5riYOucPx84DOE74xIhKENOWgBjK3XVEs= +github.com/hashicorp/go-getter/v2 v2.1.0 h1:MsLbi7yFKGFPVmpK+un4/k5HFry0tqvo9JppsCmIutU= +github.com/hashicorp/go-getter/v2 v2.1.0/go.mod h1:w65fE5glbccYjndAuj1kA5lnVBGZYEaH0e5qA1kpIks= +github.com/hashicorp/go-hclog v0.0.0-20180709165350-ff2cf002a8dd/go.mod h1:9bjs9uLqI8l75knNv3lV1kA55veR+WUPSiKIWcQHudI= +github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ= +github.com/hashicorp/go-hclog v0.12.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= +github.com/hashicorp/go-hclog v0.14.1/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= +github.com/hashicorp/go-hclog v0.16.1/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= +github.com/hashicorp/go-hclog v0.16.2 h1:K4ev2ib4LdQETX5cSZBG0DVLk1jwGqSPXBjdah3veNs= +github.com/hashicorp/go-hclog v0.16.2/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= +github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= +github.com/hashicorp/go-immutable-radix v1.1.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= +github.com/hashicorp/go-immutable-radix v1.3.1 h1:DKHmCUm2hRBK510BaiZlwvpD40f8bJFeZnpfm2KLowc= +github.com/hashicorp/go-immutable-radix v1.3.1/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= +github.com/hashicorp/go-kms-wrapping/entropy v0.1.0/go.mod h1:d1g9WGtAunDNpek8jUIEJnBlbgKS1N2Q61QkHiZyR1g= +github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= +github.com/hashicorp/go-msgpack v0.5.5 h1:i9R9JSrqIz0QVLz3sz+i3YJdT7TTSLcfLLzJi9aZTuI= +github.com/hashicorp/go-msgpack v0.5.5/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= +github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= +github.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+vmowP0z+KUhOZdA= +github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= +github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= +github.com/hashicorp/go-plugin v1.0.1/go.mod h1:++UyYGoz3o5w9ZzAdZxtQKrWWP+iqPBn3cQptSMzBuY= +github.com/hashicorp/go-retryablehttp v0.5.3/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs= +github.com/hashicorp/go-retryablehttp v0.6.2/go.mod h1:gEx6HMUGxYYhJScX7W1Il64m6cc2C1mDaW3NQ9sY1FY= +github.com/hashicorp/go-retryablehttp v0.6.6/go.mod h1:vAew36LZh98gCBJNLH42IQ1ER/9wtLZZ8meHqQvEYWY= +github.com/hashicorp/go-retryablehttp v0.7.0 h1:eu1EI/mbirUgP5C8hVsTNaGZreBDlYiwC1FZWkvQPQ4= +github.com/hashicorp/go-retryablehttp v0.7.0/go.mod h1:vAew36LZh98gCBJNLH42IQ1ER/9wtLZZ8meHqQvEYWY= +github.com/hashicorp/go-rootcerts v1.0.1/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8= +github.com/hashicorp/go-rootcerts v1.0.2 h1:jzhAVGtqPKbwpyCPELlgNWhE1znq+qwJtW5Oi2viEzc= +github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8= +github.com/hashicorp/go-safetemp v1.0.0 h1:2HR189eFNrjHQyENnQMMpCiBAsRxzbTMIgBhEyExpmo= +github.com/hashicorp/go-safetemp v1.0.0/go.mod h1:oaerMy3BhqiTbVye6QuFhFtIceqFoDHxNAB65b+Rj1I= +github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= +github.com/hashicorp/go-sockaddr v1.0.2 h1:ztczhD1jLxIRjVejw8gFomI1BQZOe2WoVOu0SyteCQc= +github.com/hashicorp/go-sockaddr v1.0.2/go.mod h1:rB4wwRAUzs07qva3c5SdrY/NEtAUjGlgmH/UkBUC97A= +github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= +github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-uuid v1.0.2 h1:cfejS+Tpcp13yd5nYHWDI6qVCny6wyX2Mt5SGur2IGE= +github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-version v1.1.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= +github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= +github.com/hashicorp/go-version v1.3.0 h1:McDWVJIU/y+u1BRV06dPaLfLCaT7fUTJLp5r04x7iNw= +github.com/hashicorp/go-version v1.3.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= +github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.3/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= +github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc= +github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= +github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= +github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/hashicorp/hcl/v2 v2.12.0 h1:PsYxySWpMD4KPaoJLnsHwtK5Qptvj/4Q6s0t4sUxZf4= +github.com/hashicorp/hcl/v2 v2.12.0/go.mod h1:FwWsfWEjyV/CMj8s/gqAuiviY72rJ1/oayI9WftqcKg= +github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= +github.com/hashicorp/mdns v1.0.1/go.mod h1:4gW7WsVCke5TE7EPeYliwHlRUyBtfCwuFwuMg2DmyNY= +github.com/hashicorp/memberlist v0.2.2 h1:5+RffWKwqJ71YPu9mWsF7ZOscZmwfasdA8kbdC7AO2g= +github.com/hashicorp/memberlist v0.2.2/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOnAH9VT3Sh9MUE= +github.com/hashicorp/packer-plugin-sdk v0.3.0 h1:G4Uze/85X3n6c+8DawHdxptOZ0vHOeJ2LAAhBFLjYmg= +github.com/hashicorp/packer-plugin-sdk v0.3.0/go.mod h1:bqpbL7w5Ee2QWrUyAsZI/MdCYpw15ls4mxgn9Ei2DZc= +github.com/hashicorp/serf v0.9.5 h1:EBWvyu9tcRszt3Bxp3KNssBMP1KuHWyO51lz9+786iM= +github.com/hashicorp/serf v0.9.5/go.mod h1:UWDWwZeL5cuWDJdl0C6wrvrUwEqtQ4ZKBKKENpqIUyk= +github.com/hashicorp/vault/api v1.0.5-0.20200519221902-385fac77e20f/go.mod h1:euTFbi2YJgwcju3imEt919lhJKF68nN1cQPq3aA+kBE= +github.com/hashicorp/vault/api v1.1.1 h1:907ld+Z9cALyvbZK2qUX9cLwvSaEQsMVQB3x2KE8+AI= +github.com/hashicorp/vault/api v1.1.1/go.mod h1:29UXcn/1cLOPHQNMWA7bCz2By4PSd0VKPAydKXS5yN0= +github.com/hashicorp/vault/sdk v0.1.14-0.20200519221530-14615acda45f/go.mod h1:WX57W2PwkrOPQ6rVQk+dy5/htHIaB4aBM70EwKThu10= +github.com/hashicorp/vault/sdk v0.2.1 h1:S4O6Iv/dyKlE9AUTXGa7VOvZmsCvg36toPKgV4f2P4M= +github.com/hashicorp/vault/sdk v0.2.1/go.mod h1:WfUiO1vYzfBkz1TmoE4ZGU7HD0T0Cl/rZwaxjBkgN4U= +github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM= +github.com/hashicorp/yamux v0.0.0-20210826001029-26ff87cf9493 h1:brI5vBRUlAlM34VFmnLPwjnCL/FxAJp9XvOdX6Zt+XE= +github.com/hashicorp/yamux v0.0.0-20210826001029-26ff87cf9493/go.mod h1:CtWFDAQgb7dxtzFs4tWbplKIe2jSi3+5vKbgIO0SLnQ= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/huandu/xstrings v1.3.2/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= +github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= +github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= +github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= +github.com/jehiah/go-strftime v0.0.0-20171201141054-1d33003b3869 h1:IPJ3dvxmJ4uczJe5YQdrYB16oTJlGSC/OyZDqUk9xX4= +github.com/jehiah/go-strftime v0.0.0-20171201141054-1d33003b3869/go.mod h1:cJ6Cj7dQo+O6GJNiMx+Pa94qKj+TG8ONdKHgMNIyyag= +github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= +github.com/jmespath/go-jmespath v0.3.0/go.mod h1:9QtRXoHjLGCJ5IBSaohpXITPlowMeeYCZ7fLUTSywik= +github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= +github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= +github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= +github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= +github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= +github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= +github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= +github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= +github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/konsorten/go-windows-terminal-sequences v1.0.3 h1:CE8S1cTafDpPvMhIxNJKvHsGVBgn1xWYf1NbHQhywc8= +github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/fs v0.1.0 h1:Jskdu9ieNAYnjxsi0LbQp1ulIKZV1LAFgK1tWhpZgl8= +github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= +github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.2.0 h1:s5hAObm+yFO5uHYt5dYjxi2rXrsnmRpJx4OYvIWUaQs= +github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348/go.mod h1:B69LEHPfb2qLo0BaaOLcbitczOKLWTsrBG9LczfCD4k= +github.com/masterzen/simplexml v0.0.0-20160608183007-4572e39b1ab9/go.mod h1:kCEbxUJlNDEBNbdQMkPSp6yaKcRXVI6f4ddk8Riv4bc= +github.com/masterzen/simplexml v0.0.0-20190410153822-31eea3082786 h1:2ZKn+w/BJeL43sCxI2jhPLRv73oVVOjEKZjKkflyqxg= +github.com/masterzen/simplexml v0.0.0-20190410153822-31eea3082786/go.mod h1:kCEbxUJlNDEBNbdQMkPSp6yaKcRXVI6f4ddk8Riv4bc= +github.com/masterzen/winrm v0.0.0-20210623064412-3b76017826b0 h1:KqYuDbSr8I2X8H65InN8SafDEa0UaLRy6WEmxDqd0F0= +github.com/masterzen/winrm v0.0.0-20210623064412-3b76017826b0/go.mod h1:l31LCh9VvG43RJ83A5JLkFPjuz48cZAxBSLQLaIn1p8= +github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= +github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= +github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-colorable v0.1.8 h1:c1ghPdyEDarC70ftn0y+A/Ee++9zz8ljHG1b13eJ0s8= +github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= +github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84= +github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE= +github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/mattn/go-isatty v0.0.13 h1:qdl+GuBjcsKKDco5BsxPJlId98mSWNKqYA+Co0SC1yA= +github.com/mattn/go-isatty v0.0.13/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= +github.com/miekg/dns v1.1.26 h1:gPxPSwALAeHJSjarOs00QjVdV9QoBvc1D2ujQUr5BzU= +github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso= +github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= +github.com/mitchellh/cli v1.1.0/go.mod h1:xcISNoH86gajksDmfB23e/pu+B+GeFRMYmoHXxx3xhI= +github.com/mitchellh/cli v1.1.2/go.mod h1:6iaV0fGdElS6dPBx0EApTxHrcWvmJphyh2n8YBLPPZ4= +github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw= +github.com/mitchellh/go-fs v0.0.0-20180402235330-b7b9ca407fff h1:bFJ74ac7ZK/jyislqiWdzrnENesFt43sNEBRh1xk/+g= +github.com/mitchellh/go-fs v0.0.0-20180402235330-b7b9ca407fff/go.mod h1:g7SZj7ABpStq3tM4zqHiVEG5un/DZ1+qJJKO7qx1EvU= +github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= +github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/go-testing-interface v0.0.0-20171004221916-a61a99592b77/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= +github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= +github.com/mitchellh/go-testing-interface v1.14.1 h1:jrgshOhYAUVNMAJiKbEu7EqAwgJJ2JqpQmpLJOu07cU= +github.com/mitchellh/go-testing-interface v1.14.1/go.mod h1:gfgS7OtZj6MA4U1UrDRp04twqAjfvlZyCfX3sDjEym8= +github.com/mitchellh/go-vnc v0.0.0-20150629162542-723ed9867aed h1:FI2NIv6fpef6BQl2u3IZX/Cj20tfypRF4yd+uaHOMtI= +github.com/mitchellh/go-vnc v0.0.0-20150629162542-723ed9867aed/go.mod h1:3rdaFaCv4AyBgu5ALFM0+tSuHrBh6v692nyQe3ikrq0= +github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo= +github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo= +github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0= +github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0= +github.com/mitchellh/iochan v1.0.0 h1:C+X3KsSTLFVBr/tK1eYN/vs4rJcvsiLU338UhYPJWeY= +github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= +github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mitchellh/mapstructure v1.3.2/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/mitchellh/mapstructure v1.4.1 h1:CpVNEelQCZBooIPDn+AR3NpivK/TIKU8bDxdASFVQag= +github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/mitchellh/reflectwalk v1.0.0 h1:9D+8oIskB4VJBN5SFlmc27fSlIBZaov1Wpk/IfikLNY= +github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= +github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= +github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d h1:VhgPp6v9qf9Agr/56bj7Y/xa04UccTW04VP0Qed4vnQ= +github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d/go.mod h1:YUTz3bUH2ZwIWBy3CJBeOBEugqcmXREj14T+iG/4k4U= +github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/opencontainers/go-digest v0.0.0-20180430190053-c9281466c8b2/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= +github.com/opencontainers/go-digest v1.0.0-rc1/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= +github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= +github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= +github.com/opencontainers/runc v0.0.0-20190115041553-12f6a991201f/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= +github.com/opencontainers/runc v0.1.1/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= +github.com/opencontainers/runtime-spec v0.1.2-0.20190507144316-5b71a03e2700/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= +github.com/packer-community/winrmcp v0.0.0-20180921211025-c76d91c1e7db h1:9uViuKtx1jrlXLBW/pMnhOfzn3iSEdLase/But/IZRU= +github.com/packer-community/winrmcp v0.0.0-20180921211025-c76d91c1e7db/go.mod h1:f6Izs6JvFTdnRbziASagjZ2vmf55NSIkC/weStxCHqk= +github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= +github.com/pascaldekloe/goe v0.1.0 h1:cBOtyMzM9HTpWjXfbbunk26uA6nG3a8n06Wieeh0MwY= +github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= +github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= +github.com/pierrec/lz4 v2.5.2+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= +github.com/pierrec/lz4 v2.6.1+incompatible h1:9UY3+iC23yxF0UfGaYrGplQ+79Rg+h/q9FV9ix19jjM= +github.com/pierrec/lz4 v2.6.1+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= +github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.8.1-0.20171018195549-f15c970de5b7/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/sftp v1.13.2 h1:taJnKntsWgU+qae21Rx52lIwndAdKrj0mfUNQsz1z4Q= +github.com/pkg/sftp v1.13.2/go.mod h1:LzqnAvaD5TWeNBsZpfKxSYn1MbjWwOsCIAFFJbpIsK8= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= +github.com/posener/complete v1.2.3/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSgv7Sy7s/s= +github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= +github.com/prometheus/client_golang v0.9.2/go.mod h1:OsXs2jCmiKlQ1lTBmv21f2mNfw4xf/QclQDMrYNZzcM= +github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= +github.com/prometheus/client_golang v1.4.0/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU= +github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/common v0.0.0-20181126121408-4724e9255275/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= +github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4= +github.com/prometheus/procfs v0.0.0-20180125133057-cb4147076ac7/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.0-20181204211112-1dc9a6cbc91a/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= +github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= +github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= +github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= +github.com/ryanuber/go-glob v1.0.0 h1:iQh3xXAumdQ+4Ufa5b25cRpC5TYKlno6hsv6Cb3pkBk= +github.com/ryanuber/go-glob v1.0.0/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc= +github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww= +github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= +github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 h1:nn5Wsu0esKSJiIVhscUtVbo7ada43DJhG55ua/hjS5I= +github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= +github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= +github.com/sirupsen/logrus v1.0.4-0.20170822132746-89742aefa4b2/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc= +github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= +github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= +github.com/sirupsen/logrus v1.6.0 h1:UBcNElsrwanuuMsnGSlYmtmgbb23qDR5dG+6X6Oo89I= +github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= +github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= +github.com/spf13/cobra v0.0.2-0.20171109065643-2da4a54c5cee/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= +github.com/spf13/pflag v1.0.1-0.20171106142849-4c012f6dcd95/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/pflag v1.0.2/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.2.0 h1:Hbg2NidpLE8veEBkEZTL3CvlkUIVzuU9jDplZO54c48= +github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/terra-farm/go-xen-api-client v0.0.2 h1:EREW0N6XqU935b005yCwbPjm8F6582j2rI5C4ew3ZTQ= +github.com/terra-farm/go-xen-api-client v0.0.2/go.mod h1:L7+Ea6rxzK7AL4DhcEPDQxdLKb4wKq0sYbxHS2ag9YE= +github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM= +github.com/ugorji/go v1.2.6 h1:tGiWC9HENWE2tqYycIqFTNorMmFRVhNwCpDOpWqnk8E= +github.com/ugorji/go v1.2.6/go.mod h1:anCg0y61KIhDlPZmnH+so+RQbysYVyDko0IMgJv0Nn0= +github.com/ugorji/go/codec v1.2.6 h1:7kbGefxLoDBuYXOms4yD7223OpNMMPNPZxXk5TvFcyQ= +github.com/ugorji/go/codec v1.2.6/go.mod h1:V6TCNZ4PHqoHGFZuSG1W8nrCzzdgA2DozYxWFFpvxTw= +github.com/ulikunitz/xz v0.5.8/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= +github.com/ulikunitz/xz v0.5.10 h1:t92gobL9l3HE202wg3rlk19F6X+JOxl9BBrCCMYEYd8= +github.com/ulikunitz/xz v0.5.10/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= +github.com/urfave/cli v0.0.0-20171014202726-7bc6a0acffa5/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= +github.com/vmihailenco/msgpack v3.3.3+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk= +github.com/vmihailenco/msgpack/v4 v4.3.12/go.mod h1:gborTTJjAo/GWTqqRjrLCn9pgNN+NXzzngzBKDPIqw4= +github.com/vmihailenco/tagparser v0.1.1/go.mod h1:OeAg3pn3UbLjkWt+rN9oFYB6u/cQgqMEUPoW2WPyhdI= +github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +github.com/zclconf/go-cty v1.2.0/go.mod h1:hOPWgoHbaTUnI5k4D2ld+GRpFJSCe6bCM7m1q/N4PQ8= +github.com/zclconf/go-cty v1.8.0/go.mod h1:vVKLxnk3puL4qRAv72AO+W99LUD4da90g3uUAzyuvAk= +github.com/zclconf/go-cty v1.10.0 h1:mp9ZXQeIcN8kAwuqorjH+Q+njbJKjLrvB2yIh4q7U+0= +github.com/zclconf/go-cty v1.10.0/go.mod h1:vVKLxnk3puL4qRAv72AO+W99LUD4da90g3uUAzyuvAk= +github.com/zclconf/go-cty-debug v0.0.0-20191215020915-b22d67c1ba0b/go.mod h1:ZRKQfBXbGkpdV6QMzT3rU1kSTAnfu1dO8dPKjYprgj8= +go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= +go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= +go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= +go.opencensus.io v0.23.0 h1:gqCw0LfLxScz8irSi8exQc7fyQ0fKQU/qnC/X8+V/1M= +go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= +go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= +go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= +golang.org/x/crypto v0.0.0-20171113213409-9f005a07e0d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190222235706-ffb98f73852f/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190418165655-df01cb2cc480/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= +golang.org/x/crypto v0.0.0-20190426145343-a29dc8fdc734/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200604202706-70a84ac30bf9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20220427172511-eb4f295cb31f h1:OeJjE6G4dgCY4PIXvIRQbE8+RX+uXZyGhUy/ksMGJoc= +golang.org/x/crypto v0.0.0-20220427172511-eb4f295cb31f/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= +golang.org/x/exp v0.0.0-20190731235908-ec7cb31e5a56/go.mod h1:JhuoJpWY28nO4Vef9tZUw9qufEGTyX1+7lmHxV5q5G4= +golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= +golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= +golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= +golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= +golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= +golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= +golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= +golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= +golang.org/x/mobile v0.0.0-20210901025245-1fde1d6c3ca1/go.mod h1:jFTmtFYCV0MFtXBU+J5V/+5AUeVS0ON/0WkE/KSrl6E= +golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= +golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180811021610-c39426892332/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191004110552-13f9640d40b9/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200602114024-627f9648deb9/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc= +golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= +golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2 h1:CIJ76btIcR3eFI5EgSo6k1qKw9KJexJuRLI9G7Hp5wE= +golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210805134026-6f1e6394065a/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f h1:Qmd2pbz05z7z6lm0DrgQVVPuBm92jqujBKMHMOlOQEw= +golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190129075346-302c3dd5f1cc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190502175342-a43fa875dd82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190514135907-3a4b5fb9f71f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190922100055-0a153f010e69/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200124204421-9fbb57f87de9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200602225109-6fdc65e7d980/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210603125802-9665404d3644/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211019181941-9d821ace8654 h1:id054HUawV2/6IGm2IV8KZQjqtwAOo2CYlOToYqa0d0= +golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b h1:9zKuko04nR4gjZ4+DNjHqRlAJqbJETHwiNKDqTfOjfE= +golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20200416051211-89c76fbcd5d1/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac h1:7zkz7BUtwNFFqcowJ+RIgu2MaV/MapERkDIy+mwPyjs= +golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190624222133-a101b041ded4/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190907020128-2ca718005c18/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= +golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE= +golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= +golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.10/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= +google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= +google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.21.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= +google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= +google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg= +google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE= +google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8= +google.golang.org/api v0.41.0/go.mod h1:RkxM5lITDfTzmyKFPt+wGrCJbVfniCr2ool8kTBzRTU= +google.golang.org/api v0.43.0/go.mod h1:nQsDGjRXMo4lvh5hP0TKqF244gqhGcr/YSIykhUk/94= +google.golang.org/api v0.47.0/go.mod h1:Wbvgpq1HddcWVtzsVLyfLp8lDg6AA241LmgIL59tHXo= +google.golang.org/api v0.48.0/go.mod h1:71Pr1vy+TAZRPkPs/xlCf5SsU8WjuAWv1Pfjbtukyy4= +google.golang.org/api v0.50.0/go.mod h1:4bNT5pAuq5ji4SRZm+5QIkjny9JAyVD/3gaSihNefaw= +google.golang.org/api v0.51.0/go.mod h1:t4HdrdoNgyN5cbEfm7Lum0lcLDLiise1F8qDKX00sOU= +google.golang.org/api v0.54.0/go.mod h1:7C4bFFOvVDGXjfDTAsgGwDgAxRDeQ4X8NvUedIt6z3k= +google.golang.org/api v0.55.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE= +google.golang.org/api v0.56.0 h1:08F9XVYTLOGeSQb3xI9C0gXMuQanhdGed0cWFhDozbI= +google.golang.org/api v0.56.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= +google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= +google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= +google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= +google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= +google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= +google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210222152913-aa3ee6e6a81c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210303154014-9728d6b83eeb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A= +google.golang.org/genproto v0.0.0-20210513213006-bf773b8c8384/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A= +google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= +google.golang.org/genproto v0.0.0-20210604141403-392c879c8b08/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= +google.golang.org/genproto v0.0.0-20210608205507-b6d2f5bf0d7d/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= +google.golang.org/genproto v0.0.0-20210624195500-8bfb893ecb84/go.mod h1:SzzZ/N+nwJDaO1kznhnlzqS8ocJICar6hYhVyhi++24= +google.golang.org/genproto v0.0.0-20210713002101-d411969a0d9a/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k= +google.golang.org/genproto v0.0.0-20210716133855-ce7ef5c701ea/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k= +google.golang.org/genproto v0.0.0-20210728212813-7823e685a01f/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48= +google.golang.org/genproto v0.0.0-20210805201207-89edb61ffb67/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48= +google.golang.org/genproto v0.0.0-20210813162853-db860fec028c/go.mod h1:cFeNkxwySK631ADgubI+/XFU/xp8FD5KIVV4rj8UC5w= +google.golang.org/genproto v0.0.0-20210821163610-241b8fcbd6c8/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= +google.golang.org/genproto v0.0.0-20210825212027-de86158e7fda/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= +google.golang.org/genproto v0.0.0-20210828152312-66f60bf46e71/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= +google.golang.org/genproto v0.0.0-20210831024726-fe130286e0e2 h1:NHN4wOCScVzKhPenJ2dt+BTs3X/XkBVI/Rh4iDt55T8= +google.golang.org/genproto v0.0.0-20210831024726-fe130286e0e2/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= +google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= +google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +google.golang.org/grpc v1.22.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.23.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= +google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= +google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= +google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= +google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8= +google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.37.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= +google.golang.org/grpc v1.37.1/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= +google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= +google.golang.org/grpc v1.39.0/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE= +google.golang.org/grpc v1.39.1/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE= +google.golang.org/grpc v1.40.0 h1:AGJ0Ih4mHjSeibYkFGh1dD9KJ/eOtZ93I6hoHhukQ5Q= +google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= +google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= +google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+RurQ= +google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +gopkg.in/airbrake/gobrake.v2 v2.0.9/go.mod h1:/h5ZAUhDkGaJfjzjKLSjv6zCL6O0LLBxU4K+aSYdM/U= +gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= +gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/gemnasium/logrus-airbrake-hook.v2 v2.1.2/go.mod h1:Xk6kEKp8OKb+X14hQBKWaSkCsqBpgog8nAV2xsGOxlo= +gopkg.in/square/go-jose.v2 v2.3.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= +gopkg.in/square/go-jose.v2 v2.5.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= +gopkg.in/square/go-jose.v2 v2.6.0 h1:NGk74WTnPKBNUhNzQX7PYcTLUjoq7mzKk2OKbvwk2iI= +gopkg.in/square/go-jose.v2 v2.6.0/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= +gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= +honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= +rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= +rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= diff --git a/main.go b/main.go new file mode 100644 index 0000000..9a39fd0 --- /dev/null +++ b/main.go @@ -0,0 +1,38 @@ +package main + +import ( + "fmt" + "os" + + "github.com/xenserver/packer-builder-xenserver/builder/xenserver/iso" + "github.com/xenserver/packer-builder-xenserver/builder/xenserver/xva" + + "github.com/hashicorp/packer-plugin-sdk/plugin" + "github.com/hashicorp/packer-plugin-sdk/version" +) + +var ( + // Version is the main version number that is being run at the moment. + Version = "v0.3.0" + + // VersionPrerelease is A pre-release marker for the Version. If this is "" + // (empty string) then it means that it is a final release. Otherwise, this + // is a pre-release such as "dev" (in development), "beta", "rc1", etc. + VersionPrerelease = "" + + // PluginVersion is used by the plugin set to allow Packer to recognize + // what version this plugin is. + PluginVersion = version.InitializePluginVersion(Version, VersionPrerelease) +) + +func main() { + pps := plugin.NewSet() + pps.RegisterBuilder("iso", new(iso.Builder)) + pps.RegisterBuilder("xva", new(xva.Builder)) + pps.SetVersion(PluginVersion) + err := pps.Run() + if err != nil { + fmt.Fprintln(os.Stderr, err.Error()) + os.Exit(1) + } +} diff --git a/meta-data b/meta-data new file mode 100644 index 0000000..e69de29 diff --git a/packer.json b/packer.json new file mode 100644 index 0000000..5fb77bd --- /dev/null +++ b/packer.json @@ -0,0 +1,6 @@ +{ + "builders": [{ + "type": "xenserver-iso", + "tools_iso_name": "guest-tools.iso" + }] +} diff --git a/test-brut.pkr.hcl b/test-brut.pkr.hcl new file mode 100644 index 0000000..5690685 --- /dev/null +++ b/test-brut.pkr.hcl @@ -0,0 +1,65 @@ +packer { + required_plugins { + xenserver= { + version = ">= v0.3.2" + source = "github.com/ddelnano/xenserver" + } + } +} + +variable "sr_iso_name" { + type = string + default = "ISOs" + description = "The ISO-SR to packer will use" + +} + +variable "sr_name" { + type = string + default = "Local storageD" + description = "The name of the SR to packer will use" +} + +locals { + timestamp = regex_replace(timestamp(), "[- TZ:]", "") +} + + +source "xenserver-iso" "ubuntu-2004" { + iso_checksum = "5035be37a7e9abbdc09f0d257f3e33416c1a0fb322ba860d42d74aa75c3468d4" + iso_checksum_type = "sha256" + iso_url = "http://releases.ubuntu.com/20.04/ubuntu-20.04.5-live-server-amd64.iso" + + sr_iso_name = var.sr_iso_name + sr_name = var.sr_name + tools_iso_name = "guest-tools.iso" + + remote_host = var.remote_host + remote_password = var.remote_password + remote_username = var.remote_username + + # Change this to match the ISO of ubuntu you are using in the iso_url variable + clone_template = "Ubuntu Focal Fossa 20.04" + vm_name = "packer-ubuntu-2004-${local.timestamp}" + vm_description = "Build started: ${local.timestamp}" + vcpus_max = 4 + vm_memory = 4096 + disk_size = 20000 + + floppy_files = [ + "examples/http/ubuntu-2004/meta-data", + "examples/http/ubuntu-2004/user-data", + ] + + ssh_username = "testuser" + ssh_password = "ubuntu" + ssh_wait_timeout = "60000s" + ssh_handshake_attempts = 10000 + + output_directory = "packer-ubuntu-2004-iso" + keep_vm = "always" +} + +build { + sources = ["xenserver-iso.ubuntu-2004"] +} diff --git a/test-debian.pkr.hcl b/test-debian.pkr.hcl new file mode 100644 index 0000000..7b061ec --- /dev/null +++ b/test-debian.pkr.hcl @@ -0,0 +1,66 @@ +packer { + required_plugins { + xenserver= { + version = ">= v0.3.2" + source = "github.com/ddelnano/xenserver" + } + } +} + +variable "sr_iso_name" { + type = string + default = "ISOs" + description = "The ISO-SR to packer will use" + +} + +variable "sr_name" { + type = string + default = "Local storageD" + description = "The name of the SR to packer will use" +} + +locals { + timestamp = regex_replace(timestamp(), "[- TZ:]", "") +} + + +source "xenserver-iso" "debian-11" { + iso_checksum = "55f6f49b32d3797621297a9481a6cc3e21b3142f57d8e1279412ff5a267868d8" + iso_checksum_type = "sha256" + iso_url = "https://cdimage.debian.org/debian-cd/current/amd64/iso-dvd/debian-11.6.0-amd64-DVD-1.iso" + + sr_iso_name = var.sr_iso_name + sr_name = var.sr_name + tools_iso_name = "guest-tools.iso" + + remote_host = var.remote_host + remote_password = var.remote_password + remote_username = var.remote_username + + # Change this to match the ISO of ubuntu you are using in the iso_url variable + clone_template = "Debian Bullseye 11" + vm_name = "packer-debian-11-${local.timestamp}" + vm_description = "Build started: ${local.timestamp}" + vcpus_max = 4 + vcpus_atstartup = 4 + vm_memory = 4096 + disk_size = 10000 + + floppy_files = [ + "debian-preseed.cfg", + ] + + ssh_username = "cecile" + ssh_password = "cecile" + ssh_wait_timeout = "60000s" + ssh_handshake_attempts = 10000 + + output_directory = "packer-debian-11-iso" + keep_vm = "always" + format = "none" +} + +build { + sources = ["xenserver-iso.debian-11"] +} diff --git a/user-data b/user-data new file mode 100644 index 0000000..9653b71 --- /dev/null +++ b/user-data @@ -0,0 +1,22 @@ +#cloud-config + +# hack for cloud-init per: +# https://github.com/leakespeake/packer/blob/3f3e361751b4be9326b66771d96f2519bc8f885e/builders/vmware/vsphere-iso/ubuntu-server-20-04/hcl2/http/ubuntu-server-subiquity/user-data +runcmd: + # to enable true auto-install for Ubuntu 20.04 with cloud-init nocloud (eliminates "Continue with autoinstall?" prompt) + - [eval, 'echo $(cat /proc/cmdline) "autoinstall" > /root/cmdline'] + - [eval, 'mount -n --bind -o ro /root/cmdline /proc/cmdline'] + - [eval, 'snap restart subiquity.subiquity-service'] + +autoinstall: + version: 1 + identity: + hostname: ubuntu-server + # This is the crypted pass of 'ubuntu' + password: "$6$exDY1mhS4KUYCE/2$zmn9ToZwTKLhCw.b4/b.ZRTIZM30JZ4QrOQ2aOXJ8yk96xpcCof0kxKwuX1kqLG/ygbJ1f8wxED22bTL4F46P0" + username: testuser + packages: + - xe-guest-utilities + ssh: + install-server: yes + allow-pw: yes diff --git a/variables.pkrvars.hcl.example b/variables.pkrvars.hcl.example new file mode 100644 index 0000000..9a6225e --- /dev/null +++ b/variables.pkrvars.hcl.example @@ -0,0 +1,21 @@ +variable "remote_host" { + type = string + description = "The ip or fqdn of your XenServer. This will be pulled from the env var 'PKR_VAR_XAPI_HOST'" + sensitive = true + default = "ip" +} + +variable "remote_password" { + type = string + description = "The password used to interact with your XenServer. This will be pulled from the env var 'PKR_VAR_XAPI_PASSWORD'" + sensitive = true + default = "password" +} + +variable "remote_username" { + type = string + description = "The username used to interact with your XenServer. This will be pulled from the env var 'PKR_VAR_XAPI_USERNAME'" + sensitive = true + default = "root" + +}