Merge remote-tracking branch 'packer-builder-vsphere/master' into cd/merge

This commit is contained in:
Chris Doherty 2019-12-09 17:15:08 -08:00
commit 1140504935
84 changed files with 7071 additions and 0 deletions

View File

@ -0,0 +1,20 @@
<component name="ProjectDictionaryState">
<dictionary name="project">
<words>
<w>abcdefghijklmnopqrstuvwxyz</w>
<w>cdrom</w>
<w>cdroms</w>
<w>datastore</w>
<w>datastores</w>
<w>esxi</w>
<w>hashicorp</w>
<w>mozilla</w>
<w>sata</w>
<w>scancode</w>
<w>vcenter</w>
<w>vmware</w>
<w>vmxnet</w>
<w>vsphere</w>
</words>
</dictionary>
</component>

2
.teamcity/.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
*.iml
target/

104
.teamcity/pom.xml vendored Normal file
View File

@ -0,0 +1,104 @@
<?xml version="1.0"?>
<project>
<modelVersion>4.0.0</modelVersion>
<name>PackerVSphere Config DSL Script</name>
<groupId>PackerVSphere</groupId>
<artifactId>PackerVSphere_dsl</artifactId>
<version>1.0-SNAPSHOT</version>
<parent>
<groupId>org.jetbrains.teamcity</groupId>
<artifactId>configs-dsl-kotlin-parent</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
<repositories>
<repository>
<id>jetbrains-all</id>
<url>https://download.jetbrains.com/teamcity-repository</url>
<snapshots>
<enabled>true</enabled>
</snapshots>
</repository>
<repository>
<id>teamcity-server</id>
<url>https://teamcity.jetbrains.com/app/dsl-plugins-repository</url>
<snapshots>
<enabled>true</enabled>
</snapshots>
</repository>
</repositories>
<pluginRepositories>
<pluginRepository>
<id>JetBrains</id>
<url>https://download.jetbrains.com/teamcity-repository</url>
</pluginRepository>
</pluginRepositories>
<build>
<sourceDirectory>.</sourceDirectory>
<plugins>
<plugin>
<artifactId>kotlin-maven-plugin</artifactId>
<groupId>org.jetbrains.kotlin</groupId>
<version>${kotlin.version}</version>
<configuration/>
<executions>
<execution>
<id>compile</id>
<phase>process-sources</phase>
<goals>
<goal>compile</goal>
</goals>
</execution>
<execution>
<id>test-compile</id>
<phase>process-test-sources</phase>
<goals>
<goal>test-compile</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.jetbrains.teamcity</groupId>
<artifactId>teamcity-configs-maven-plugin</artifactId>
<version>${teamcity.dsl.version}</version>
<configuration>
<format>kotlin</format>
<dstDir>target/generated-configs</dstDir>
</configuration>
</plugin>
</plugins>
</build>
<dependencies>
<dependency>
<groupId>org.jetbrains.teamcity</groupId>
<artifactId>configs-dsl-kotlin</artifactId>
<version>${teamcity.dsl.version}</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.jetbrains.teamcity</groupId>
<artifactId>configs-dsl-kotlin-plugins</artifactId>
<version>1.0-SNAPSHOT</version>
<type>pom</type>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-stdlib-jdk8</artifactId>
<version>${kotlin.version}</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-script-runtime</artifactId>
<version>${kotlin.version}</version>
<scope>compile</scope>
</dependency>
</dependencies>
</project>

137
.teamcity/settings.kts vendored Normal file
View File

@ -0,0 +1,137 @@
import jetbrains.buildServer.configs.kotlin.v2018_2.*
import jetbrains.buildServer.configs.kotlin.v2018_2.buildFeatures.PullRequests
import jetbrains.buildServer.configs.kotlin.v2018_2.buildFeatures.commitStatusPublisher
import jetbrains.buildServer.configs.kotlin.v2018_2.buildFeatures.pullRequests
import jetbrains.buildServer.configs.kotlin.v2018_2.buildSteps.dockerCompose
import jetbrains.buildServer.configs.kotlin.v2018_2.buildSteps.script
import jetbrains.buildServer.configs.kotlin.v2018_2.triggers.vcs
import jetbrains.buildServer.configs.kotlin.v2018_2.vcs.GitVcsRoot
version = "2018.2"
project {
description = "https://github.com/jetbrains-infra/packer-builder-vsphere"
vcsRoot(GitHub)
buildType(Build)
features {
feature {
type = "OAuthProvider"
param("providerType", "GitHub")
param("displayName", "GitHub.com")
param("gitHubUrl", "https://github.com/")
param("clientId", "1abfd46417d7795298a1")
param("secure:clientSecret", "credentialsJSON:5fe99dc3-4d1d-4fd6-9f5c-e87fbcbd9a4e")
param("defaultTokenScope", "public_repo,repo,repo:status,write:repo_hook")
}
feature {
type = "IssueTracker"
param("name", "packer-builder-vsphere")
param("type", "GithubIssues")
param("repository", "https://github.com/jetbrains-infra/packer-builder-vsphere")
param("authType", "anonymous")
param("pattern", """#(\d+)""")
}
}
}
object GitHub : GitVcsRoot({
name = "packer-builder-vsphere"
url = "https://github.com/jetbrains-infra/packer-builder-vsphere"
branch = "master"
branchSpec = "+:refs/heads/(*)"
userNameStyle = GitVcsRoot.UserNameStyle.FULL
})
object Build : BuildType({
val golangImage = "jetbrainsinfra/golang:1.11.4"
name = "Build"
vcs {
root(GitHub)
}
requirements {
equals("docker.server.osType", "linux")
exists("dockerCompose.version")
doesNotContain("teamcity.agent.name", "ubuntu-single-build")
}
params {
param("env.GOPATH", "%teamcity.build.checkoutDir%/build/modules")
param("env.GOCACHE", "%teamcity.build.checkoutDir%/build/cache")
password("env.VPN_PASSWORD", "credentialsJSON:8c355e81-9a26-4788-8fea-c854cd646c35")
param ("env.VSPHERE_USERNAME", """vsphere65.test\teamcity""")
password("env.VSPHERE_PASSWORD", "credentialsJSON:d5e7ac7f-357b-464a-b2fa-ddd4c433b22b")
}
steps {
script {
name = "Build"
scriptContent = "make build -j 3"
dockerImage = golangImage
dockerPull = true
}
dockerCompose {
name = "Start VPN tunnel"
file = "teamcity-services.yml"
}
script {
name = "Test"
scriptContent = "make test | go-test-teamcity"
dockerImage = golangImage
dockerPull = true
dockerRunParameters = "--network=container:vpn"
}
script {
name = "gofmt"
executionMode = BuildStep.ExecutionMode.RUN_ON_FAILURE
scriptContent = "./gofmt.sh"
dockerImage = golangImage
dockerPull = true
}
}
features {
commitStatusPublisher {
publisher = github {
githubUrl = "https://api.github.com"
authType = personalToken {
token = "credentialsJSON:5ead3bb1-c370-4589-beb8-24f8d02c36bc"
}
}
}
pullRequests {
provider = github {
authType = token {
token = "credentialsJSON:5ead3bb1-c370-4589-beb8-24f8d02c36bc"
}
filterAuthorRole = PullRequests.GitHubRoleFilter.EVERYBODY
}
}
}
triggers {
vcs {
triggerRules = """
-:*.md
-:.teamcity/
""".trimIndent()
branchFilter = """
+:*
-:temp-*
-:pull/*
""".trimIndent()
}
}
maxRunningBuilds = 2
artifactRules = "bin/* => packer-builder-vsphere-%build.number%.zip"
allowExternalStatus = true
})

9
builder/vsphere/.gitignore vendored Normal file
View File

@ -0,0 +1,9 @@
.idea/
packer-builder-vsphere*
build/
bin/
.env
test*.json
crash.log
packer_cache/
vendor/

373
builder/vsphere/LICENSE.txt Normal file
View File

@ -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.

36
builder/vsphere/Makefile Normal file
View File

@ -0,0 +1,36 @@
GOOPTS := GOARCH=amd64 CGO_ENABLED=0
build: iso clone
iso: iso-linux iso-windows iso-macos
clone: clone-linux clone-windows clone-macos
iso-linux: modules bin
$(GOOPTS) GOOS=linux go build -o bin/packer-builder-vsphere-iso.linux ./cmd/iso
iso-windows: modules bin
$(GOOPTS) GOOS=windows go build -o bin/packer-builder-vsphere-iso.exe ./cmd/iso
iso-macos: modules bin
$(GOOPTS) GOOS=darwin go build -o bin/packer-builder-vsphere-iso.macos ./cmd/iso
clone-linux: modules bin
$(GOOPTS) GOOS=linux go build -o bin/packer-builder-vsphere-clone.linux ./cmd/clone
clone-windows: modules bin
$(GOOPTS) GOOS=windows go build -o bin/packer-builder-vsphere-clone.exe ./cmd/clone
clone-macos: modules bin
$(GOOPTS) GOOS=darwin go build -o bin/packer-builder-vsphere-clone.macos ./cmd/clone
modules:
go mod download
bin:
mkdir -p bin
rm -f bin/*
test:
PACKER_ACC=1 go test -v -count 1 ./driver ./iso ./clone
.PHONY: bin test

192
builder/vsphere/README.md Normal file
View File

@ -0,0 +1,192 @@
[![Team project](http://jb.gg/badges/team.svg)](https://confluence.jetbrains.com/display/ALL/JetBrains+on+GitHub)
[![GitHub latest release](https://img.shields.io/github/release/jetbrains-infra/packer-builder-vsphere.svg)](https://github.com/jetbrains-infra/packer-builder-vsphere/releases)
[![GitHub downloads](https://img.shields.io/github/downloads/jetbrains-infra/packer-builder-vsphere/total.svg)](https://github.com/jetbrains-infra/packer-builder-vsphere/releases)
[![TeamCity build status](https://img.shields.io/teamcity/http/teamcity.jetbrains.com/s/PackerVSphere_Build.svg)](https://teamcity.jetbrains.com/viewType.html?buildTypeId=PackerVSphere_Build&guest=1)
# Packer Builder for VMware vSphere
This a plugin for [HashiCorp Packer](https://www.packer.io/). It uses native vSphere API, and creates virtual machines remotely.
`vsphere-iso` builder creates new VMs from scratch.
`vsphere-clone` builder clones VMs from existing templates.
- VMware Player is not required.
- Official vCenter API is used, no ESXi host [modification](https://www.packer.io/docs/builders/vmware-iso.html#building-on-a-remote-vsphere-hypervisor) is required.
## Installation
* Download binaries from the [releases page](https://github.com/jetbrains-infra/packer-builder-vsphere/releases).
* [Install](https://www.packer.io/docs/extending/plugins.html#installing-plugins) the plugins, or simply put them into the same directory with JSON templates. On Linux and macOS run `chmod +x` on the files.
## Build
Install Go and [dep](https://github.com/golang/dep/releases), run `build.sh`.
Or build inside a container by Docker Compose:
```
docker-compose run build
```
The binaries will be in `bin/` directory.
Artifacts can be also downloaded from [TeamCity builds](https://teamcity.jetbrains.com/viewLog.html?buildTypeId=PackerVSphere_Build&buildId=lastSuccessful&tab=artifacts&guest=1).
## Examples
See complete Ubuntu, Windows, and macOS templates in the [examples folder](https://github.com/jetbrains-infra/packer-builder-vsphere/tree/master/examples/).
## Parameter Reference
### Connection
* `vcenter_server`(string) - vCenter server hostname.
* `username`(string) - vSphere username.
* `password`(string) - vSphere password.
* `insecure_connection`(boolean) - Do not validate vCenter server's TLS certificate. Defaults to `false`.
* `datacenter`(string) - VMware datacenter name. Required if there is more than one datacenter in vCenter.
### VM Location
* `vm_name`(string) - Name of the new VM to create.
* `folder`(string) - VM folder to create the VM in.
* `host`(string) - ESXi host where target VM is created. A full path must be specified if the host is in a folder. For example `folder/host`. See the `Specifying Clusters and Hosts` section above for more details.
* `cluster`(string) - ESXi cluster where target VM is created. See [Working with Clusters](#working-with-clusters).
* `resource_pool`(string) - VMWare resource pool. Defaults to the root resource pool of the `host` or `cluster`.
* `datastore`(string) - VMWare datastore. Required if `host` is a cluster, or if `host` has multiple datastores.
* `notes`(string) - VM notes.
### VM Location (`vsphere-clone` only)
* `template`(string) - Name of source VM. Path is optional.
* `linked_clone`(boolean) - Create VM as a linked clone from latest snapshot. Defaults to `false`.
### Hardware
* `CPUs`(number) - Number of CPU sockets.
* `cpu_cores`(number) - Number of CPU cores per socket.
* `CPU_limit`(number) - Upper limit of available CPU resources in MHz.
* `CPU_reservation`(number) - Amount of reserved CPU resources in MHz.
* `CPU_hot_plug`(boolean) - Enable CPU hot plug setting for virtual machine. Defaults to `false`.
* `RAM`(number) - Amount of RAM in MB.
* `RAM_reservation`(number) - Amount of reserved RAM in MB.
* `RAM_reserve_all`(boolean) - Reserve all available RAM. Defaults to `false`. Cannot be used together with `RAM_reservation`.
* `RAM_hot_plug`(boolean) - Enable RAM hot plug setting for virtual machine. Defaults to `false`.
* `video_ram`(number) - Amount of video memory in MB.
* `disk_size`(number) - The size of the disk in MB.
* `network`(string) - Set network VM will be connected to.
* `NestedHV`(boolean) - Enable nested hardware virtualization for VM. Defaults to `false`.
* `configuration_parameters`(map) - Custom parameters.
* `boot_order`(string) - Priority of boot devices. Defaults to `disk,cdrom`
### Hardware (`vsphere-iso` only)
* `vm_version`(number) - Set VM hardware version. Defaults to the most current VM hardware version supported by vCenter. See [VMWare article 1003746](https://kb.vmware.com/s/article/1003746) for the full list of supported VM hardware versions.
* `guest_os_type`(string) - Set VM OS type. Defaults to `otherGuest`. See [here](https://pubs.vmware.com/vsphere-6-5/index.jsp?topic=%2Fcom.vmware.wssdk.apiref.doc%2Fvim.vm.GuestOsDescriptor.GuestOsIdentifier.html) for a full list of possible values.
* `disk_controller_type`(string) - Set VM disk controller type. Example `pvscsi`.
* `disk_thin_provisioned`(boolean) - Enable VMDK thin provisioning for VM. Defaults to `false`.
* `network_card`(string) - Set VM network card type. Example `vmxnet3`.
* `usb_controller`(boolean) - Create USB controller for virtual machine. Defaults to `false`.
* `cdrom_type`(string) - Which controller to use. Example `sata`. Defaults to `ide`.
* `firmware`(string) - Set the Firmware at machine creation. Example `efi`. Defaults to `bios`.
### Boot (`vsphere-iso` only)
* `iso_paths`(array of strings) - List of datastore paths to ISO files that will be mounted to the VM. Example `"[datastore1] ISO/ubuntu.iso"`.
* `floppy_files`(array of strings) - List of local files to be mounted to the VM floppy drive. Can be used to make Debian preseed or RHEL kickstart files available to the VM.
* `floppy_dirs`(array of strings) - List of directories to copy files from.
* `floppy_img_path`(string) - Datastore path to a floppy image that will be mounted to the VM. Example `[datastore1] ISO/pvscsi-Windows8.flp`.
* `http_directory`(string) - Path to a directory to serve using a local HTTP server. Beware of [limitations](https://github.com/jetbrains-infra/packer-builder-vsphere/issues/108#issuecomment-449634324).
* `http_ip`(string) - Specify IP address on which the HTTP server is started. If not provided the first non-loopback interface is used.
* `http_port_min` and `http_port_max` as in other [builders](https://www.packer.io/docs/builders/virtualbox-iso.html#http_port_min).
* `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.
* `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. At least one of iso_checksum and iso_checksum_url must be defined. This has precedence over iso_checksum_url type.
* `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_checksum_url`(string) - A URL to a GNU or BSD style checksum file containing a checksum for the OS ISO file. At least one of iso_checksum and iso_checksum_url must be defined. This will be ignored if iso_checksum is non empty.
* `boot_wait`(string) Amount of time to wait for the VM to boot. Examples 45s and 10m. Defaults to 10 seconds. See [format](https://golang.org/pkg/time/#ParseDuration).
* `boot_command`(array of strings) - List of commands to type when the VM is first booted. Used to initalize the operating system installer. See details in [Packer docs](https://www.packer.io/docs/builders/virtualbox-iso.html#boot-command).
### Provision
* `communicator` - `ssh` (default), `winrm`, or `none` (create/clone, customize hardware, but do not boot).
* `ip_wait_timeout`(string) - Amount of time to wait for VM's IP, similar to 'ssh_timeout'. Defaults to 30m (30 minutes). See the Go Lang [ParseDuration](https://golang.org/pkg/time/#ParseDuration) documentation for full details.
* `ip_settle_timeout`(string) - Amount of time to wait for VM's IP to settle down, sometimes VM may report incorrect IP initially, then its recommended to set that parameter to apx. 2 minutes. Examples 45s and 10m. Defaults to 5s(5 seconds). See the Go Lang [ParseDuration](https://golang.org/pkg/time/#ParseDuration) documentation for full details.
* `ssh_username`(string) - Username in guest OS.
* `ssh_password`(string) - Password to access guest OS. Only specify `ssh_password` or `ssh_private_key_file`, but not both.
* `ssh_private_key_file`(string) - Path to the SSH private key file to access guest OS. Only specify `ssh_password` or `ssh_private_key_file`, but not both.
* `winrm_username`(string) - Username in guest OS.
* `winrm_password`(string) - Password to access guest OS.
* `shutdown_command`(string) - Specify a VM guest shutdown command. VMware guest tools are used by default.
* `shutdown_timeout`(string) - Amount of time to wait for graceful VM shutdown. Examples 45s and 10m. Defaults to 5m(5 minutes). See the Go Lang [ParseDuration](https://golang.org/pkg/time/#ParseDuration) documentation for full details.
### Postprocessing
* `create_snapshot`(boolean) - Create a snapshot when set to `true`, so the VM can be used as a base for linked clones. Defaults to `false`.
* `convert_to_template`(boolean) - Convert VM to a template. Defaults to `false`.
## Working with Clusters
#### Standalone Hosts
Only use the `host` option. Optionally specify a `resource_pool`:
```
"host": "esxi-1.vsphere65.test",
"resource_pool": "pool1",
```
#### Clusters Without DRS
Use the `cluster` and `host `parameters:
```
"cluster": "cluster1",
"host": "esxi-2.vsphere65.test",
```
#### Clusters With DRS
Only use the `cluster` option. Optionally specify a `resource_pool`:
```
"cluster": "cluster2",
"resource_pool": "pool1",
```
## Required vSphere Permissions
* VM folder (this object and children):
```
Virtual machine -> Inventory
Virtual machine -> Configuration
Virtual machine -> Interaction
Virtual machine -> Snapshot management
Virtual machine -> Provisioning
```
Individual privileges are listed in https://github.com/jetbrains-infra/packer-builder-vsphere/issues/97#issuecomment-436063235.
* Resource pool, host, or cluster (this object):
```
Resource -> Assign virtual machine to resource pool
```
* Host in clusters without DRS (this object):
```
Read-only
```
* Datastore (this object):
```
Datastore -> Allocate space
Datastore -> Browse datastore
Datastore -> Low level file operations
```
* Network (this object):
```
Network -> Assign network
```
* Distributed switch (this object):
```
Read-only
```
For floppy image upload:
* Datacenter (this object):
```
Datastore -> Low level file operations
```
* Host (this object):
```
Host -> Configuration -> System Management
```

View File

@ -0,0 +1,98 @@
package clone
import (
"context"
packerCommon "github.com/hashicorp/packer/common"
"github.com/hashicorp/packer/helper/communicator"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
"github.com/jetbrains-infra/packer-builder-vsphere/common"
"github.com/jetbrains-infra/packer-builder-vsphere/driver"
)
type Builder struct {
config *Config
runner multistep.Runner
}
func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
c, warnings, errs := NewConfig(raws...)
if errs != nil {
return warnings, errs
}
b.config = c
return warnings, nil
}
func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (packer.Artifact, error) {
state := new(multistep.BasicStateBag)
state.Put("comm", &b.config.Comm)
state.Put("hook", hook)
state.Put("ui", ui)
var steps []multistep.Step
steps = append(steps,
&common.StepConnect{
Config: &b.config.ConnectConfig,
},
&StepCloneVM{
Config: &b.config.CloneConfig,
Location: &b.config.LocationConfig,
Force: b.config.PackerConfig.PackerForce,
},
&common.StepConfigureHardware{
Config: &b.config.HardwareConfig,
},
&common.StepConfigParams{
Config: &b.config.ConfigParamsConfig,
},
)
if b.config.Comm.Type != "none" {
steps = append(steps,
&common.StepRun{
Config: &b.config.RunConfig,
SetOrder: false,
},
&common.StepWaitForIp{
Config: &b.config.WaitIpConfig,
},
&communicator.StepConnect{
Config: &b.config.Comm,
Host: common.CommHost(b.config.Comm.SSHHost),
SSHConfig: b.config.Comm.SSHConfigFunc(),
},
&packerCommon.StepProvision{},
&common.StepShutdown{
Config: &b.config.ShutdownConfig,
},
)
}
steps = append(steps,
&common.StepCreateSnapshot{
CreateSnapshot: b.config.CreateSnapshot,
},
&common.StepConvertToTemplate{
ConvertToTemplate: b.config.ConvertToTemplate,
},
)
b.runner = packerCommon.NewRunner(steps, b.config.PackerConfig, ui)
b.runner.Run(ctx, state)
if rawErr, ok := state.GetOk("error"); ok {
return nil, rawErr.(error)
}
if _, ok := state.GetOk("vm"); !ok {
return nil, nil
}
artifact := &common.Artifact{
Name: b.config.VMName,
VM: state.Get("vm").(*driver.VirtualMachine),
}
return artifact, nil
}

View File

@ -0,0 +1,716 @@
package clone
import (
builderT "github.com/hashicorp/packer/helper/builder/testing"
"github.com/hashicorp/packer/packer"
"github.com/jetbrains-infra/packer-builder-vsphere/common"
commonT "github.com/jetbrains-infra/packer-builder-vsphere/common/testing"
"github.com/vmware/govmomi/vim25/types"
"os"
"testing"
)
func TestCloneBuilderAcc_default(t *testing.T) {
config := defaultConfig()
builderT.Test(t, builderT.TestCase{
Builder: &Builder{},
Template: commonT.RenderConfig(config),
Check: checkDefault(t, config["vm_name"].(string), config["host"].(string), "datastore1"),
})
}
func defaultConfig() map[string]interface{} {
username := os.Getenv("VSPHERE_USERNAME")
if username == "" {
username = "root"
}
password := os.Getenv("VSPHERE_PASSWORD")
if password == "" {
password = "jetbrains"
}
config := map[string]interface{}{
"vcenter_server": "vcenter.vsphere65.test",
"username": username,
"password": password,
"insecure_connection": true,
"template": "alpine",
"host": "esxi-1.vsphere65.test",
"linked_clone": true, // speed up
"communicator": "none",
}
config["vm_name"] = commonT.NewVMName()
return config
}
func checkDefault(t *testing.T, name string, host string, datastore string) builderT.TestCheckFunc {
return func(artifacts []packer.Artifact) error {
d := commonT.TestConn(t)
vm := commonT.GetVM(t, d, artifacts)
vmInfo, err := vm.Info("name", "parent", "runtime.host", "resourcePool", "datastore")
if err != nil {
t.Fatalf("Cannot read VM properties: %v", err)
}
if vmInfo.Name != name {
t.Errorf("Invalid VM name: expected '%v', got '%v'", name, vmInfo.Name)
}
f := d.NewFolder(vmInfo.Parent)
folderPath, err := f.Path()
if err != nil {
t.Fatalf("Cannot read folder name: %v", err)
}
if folderPath != "" {
t.Errorf("Invalid folder: expected '/', got '%v'", folderPath)
}
h := d.NewHost(vmInfo.Runtime.Host)
hostInfo, err := h.Info("name")
if err != nil {
t.Fatal("Cannot read host properties: ", err)
}
if hostInfo.Name != host {
t.Errorf("Invalid host name: expected '%v', got '%v'", host, hostInfo.Name)
}
p := d.NewResourcePool(vmInfo.ResourcePool)
poolPath, err := p.Path()
if err != nil {
t.Fatalf("Cannot read resource pool name: %v", err)
}
if poolPath != "" {
t.Errorf("Invalid resource pool: expected '/', got '%v'", poolPath)
}
dsr := vmInfo.Datastore[0].Reference()
ds := d.NewDatastore(&dsr)
dsInfo, err := ds.Info("name")
if err != nil {
t.Fatal("Cannot read datastore properties: ", err)
}
if dsInfo.Name != datastore {
t.Errorf("Invalid datastore name: expected '%v', got '%v'", datastore, dsInfo.Name)
}
return nil
}
}
func TestCloneBuilderAcc_artifact(t *testing.T) {
config := defaultConfig()
builderT.Test(t, builderT.TestCase{
Builder: &Builder{},
Template: commonT.RenderConfig(config),
Check: checkArtifact(t),
})
}
func checkArtifact(t *testing.T) builderT.TestCheckFunc {
return func(artifacts []packer.Artifact) error {
if len(artifacts) > 1 {
t.Fatal("more than 1 artifact")
}
artifactRaw := artifacts[0]
_, ok := artifactRaw.(*common.Artifact)
if !ok {
t.Fatalf("unknown artifact: %#v", artifactRaw)
}
return nil
}
}
func TestCloneBuilderAcc_folder(t *testing.T) {
builderT.Test(t, builderT.TestCase{
Builder: &Builder{},
Template: folderConfig(),
Check: checkFolder(t, "folder1/folder2"),
})
}
func folderConfig() string {
config := defaultConfig()
config["folder"] = "folder1/folder2"
return commonT.RenderConfig(config)
}
func checkFolder(t *testing.T, folder string) builderT.TestCheckFunc {
return func(artifacts []packer.Artifact) error {
d := commonT.TestConn(t)
vm := commonT.GetVM(t, d, artifacts)
vmInfo, err := vm.Info("parent")
if err != nil {
t.Fatalf("Cannot read VM properties: %v", err)
}
f := d.NewFolder(vmInfo.Parent)
path, err := f.Path()
if err != nil {
t.Fatalf("Cannot read folder name: %v", err)
}
if path != folder {
t.Errorf("Wrong folder. expected: %v, got: %v", folder, path)
}
return nil
}
}
func TestCloneBuilderAcc_resourcePool(t *testing.T) {
builderT.Test(t, builderT.TestCase{
Builder: &Builder{},
Template: resourcePoolConfig(),
Check: checkResourcePool(t, "pool1/pool2"),
})
}
func resourcePoolConfig() string {
config := defaultConfig()
config["resource_pool"] = "pool1/pool2"
return commonT.RenderConfig(config)
}
func checkResourcePool(t *testing.T, pool string) builderT.TestCheckFunc {
return func(artifacts []packer.Artifact) error {
d := commonT.TestConn(t)
vm := commonT.GetVM(t, d, artifacts)
vmInfo, err := vm.Info("resourcePool")
if err != nil {
t.Fatalf("Cannot read VM properties: %v", err)
}
p := d.NewResourcePool(vmInfo.ResourcePool)
path, err := p.Path()
if err != nil {
t.Fatalf("Cannot read resource pool name: %v", err)
}
if path != pool {
t.Errorf("Wrong folder. expected: %v, got: %v", pool, path)
}
return nil
}
}
func TestCloneBuilderAcc_datastore(t *testing.T) {
builderT.Test(t, builderT.TestCase{
Builder: &Builder{},
Template: datastoreConfig(),
Check: checkDatastore(t, "datastore1"), // on esxi-1.vsphere65.test
})
}
func datastoreConfig() string {
config := defaultConfig()
config["template"] = "alpine-host4" // on esxi-4.vsphere65.test
config["linked_clone"] = false
return commonT.RenderConfig(config)
}
func checkDatastore(t *testing.T, name string) builderT.TestCheckFunc {
return func(artifacts []packer.Artifact) error {
d := commonT.TestConn(t)
vm := commonT.GetVM(t, d, artifacts)
vmInfo, err := vm.Info("datastore")
if err != nil {
t.Fatalf("Cannot read VM properties: %v", err)
}
n := len(vmInfo.Datastore)
if n != 1 {
t.Fatalf("VM should have 1 datastore, got %v", n)
}
ds := d.NewDatastore(&vmInfo.Datastore[0])
info, err := ds.Info("name")
if err != nil {
t.Fatalf("Cannot read datastore properties: %v", err)
}
if info.Name != name {
t.Errorf("Wrong datastore. expected: %v, got: %v", name, info.Name)
}
return nil
}
}
func TestCloneBuilderAcc_multipleDatastores(t *testing.T) {
t.Skip("test must fail")
builderT.Test(t, builderT.TestCase{
Builder: &Builder{},
Template: multipleDatastoresConfig(),
})
}
func multipleDatastoresConfig() string {
config := defaultConfig()
config["host"] = "esxi-4.vsphere65.test" // host with 2 datastores
config["linked_clone"] = false
return commonT.RenderConfig(config)
}
func TestCloneBuilderAcc_fullClone(t *testing.T) {
builderT.Test(t, builderT.TestCase{
Builder: &Builder{},
Template: fullCloneConfig(),
Check: checkFullClone(t),
})
}
func fullCloneConfig() string {
config := defaultConfig()
config["linked_clone"] = false
return commonT.RenderConfig(config)
}
func checkFullClone(t *testing.T) builderT.TestCheckFunc {
return func(artifacts []packer.Artifact) error {
d := commonT.TestConn(t)
vm := commonT.GetVM(t, d, artifacts)
vmInfo, err := vm.Info("layoutEx.disk")
if err != nil {
t.Fatalf("Cannot read VM properties: %v", err)
}
if len(vmInfo.LayoutEx.Disk[0].Chain) != 1 {
t.Error("Not a full clone")
}
return nil
}
}
func TestCloneBuilderAcc_linkedClone(t *testing.T) {
builderT.Test(t, builderT.TestCase{
Builder: &Builder{},
Template: linkedCloneConfig(),
Check: checkLinkedClone(t),
})
}
func linkedCloneConfig() string {
config := defaultConfig()
config["linked_clone"] = true
return commonT.RenderConfig(config)
}
func checkLinkedClone(t *testing.T) builderT.TestCheckFunc {
return func(artifacts []packer.Artifact) error {
d := commonT.TestConn(t)
vm := commonT.GetVM(t, d, artifacts)
vmInfo, err := vm.Info("layoutEx.disk")
if err != nil {
t.Fatalf("Cannot read VM properties: %v", err)
}
if len(vmInfo.LayoutEx.Disk[0].Chain) != 2 {
t.Error("Not a linked clone")
}
return nil
}
}
func TestCloneBuilderAcc_network(t *testing.T) {
builderT.Test(t, builderT.TestCase{
Builder: &Builder{},
Template: networkConfig(),
Check: checkNetwork(t, "VM Network 2"),
})
}
func networkConfig() string {
config := defaultConfig()
config["template"] = "alpine-host4"
config["host"] = "esxi-4.vsphere65.test"
config["datastore"] = "datastore4"
config["network"] = "VM Network 2"
return commonT.RenderConfig(config)
}
func checkNetwork(t *testing.T, name string) builderT.TestCheckFunc {
return func(artifacts []packer.Artifact) error {
d := commonT.TestConn(t)
vm := commonT.GetVM(t, d, artifacts)
vmInfo, err := vm.Info("network")
if err != nil {
t.Fatalf("Cannot read VM properties: %v", err)
}
n := len(vmInfo.Network)
if n != 1 {
t.Fatalf("VM should have 1 network, got %v", n)
}
ds := d.NewNetwork(&vmInfo.Network[0])
info, err := ds.Info("name")
if err != nil {
t.Fatalf("Cannot read network properties: %v", err)
}
if info.Name != name {
t.Errorf("Wrong network. expected: %v, got: %v", name, info.Name)
}
return nil
}
}
func TestCloneBuilderAcc_hardware(t *testing.T) {
builderT.Test(t, builderT.TestCase{
Builder: &Builder{},
Template: hardwareConfig(),
Check: checkHardware(t),
})
}
func hardwareConfig() string {
config := defaultConfig()
config["CPUs"] = 2
config["cpu_cores"] = 2
config["CPU_reservation"] = 1000
config["CPU_limit"] = 1500
config["RAM"] = 2048
config["RAM_reservation"] = 1024
config["CPU_hot_plug"] = true
config["RAM_hot_plug"] = true
config["video_ram"] = 8192
return commonT.RenderConfig(config)
}
func checkHardware(t *testing.T) builderT.TestCheckFunc {
return func(artifacts []packer.Artifact) error {
d := commonT.TestConn(t)
vm := commonT.GetVM(t, d, artifacts)
vmInfo, err := vm.Info("config")
if err != nil {
t.Fatalf("Cannot read VM properties: %v", err)
}
cpuSockets := vmInfo.Config.Hardware.NumCPU
if cpuSockets != 2 {
t.Errorf("VM should have 2 CPU sockets, got %v", cpuSockets)
}
cpuCores := vmInfo.Config.Hardware.NumCoresPerSocket
if cpuCores != 2 {
t.Errorf("VM should have 2 CPU cores per socket, got %v", cpuCores)
}
cpuReservation := *vmInfo.Config.CpuAllocation.Reservation
if cpuReservation != 1000 {
t.Errorf("VM should have CPU reservation for 1000 Mhz, got %v", cpuReservation)
}
cpuLimit := *vmInfo.Config.CpuAllocation.Limit
if cpuLimit != 1500 {
t.Errorf("VM should have CPU reservation for 1500 Mhz, got %v", cpuLimit)
}
ram := vmInfo.Config.Hardware.MemoryMB
if ram != 2048 {
t.Errorf("VM should have 2048 MB of RAM, got %v", ram)
}
ramReservation := *vmInfo.Config.MemoryAllocation.Reservation
if ramReservation != 1024 {
t.Errorf("VM should have RAM reservation for 1024 MB, got %v", ramReservation)
}
cpuHotAdd := vmInfo.Config.CpuHotAddEnabled
if !*cpuHotAdd {
t.Errorf("VM should have CPU hot add enabled, got %v", cpuHotAdd)
}
memoryHotAdd := vmInfo.Config.MemoryHotAddEnabled
if !*memoryHotAdd {
t.Errorf("VM should have Memory hot add enabled, got %v", memoryHotAdd)
}
l, err := vm.Devices()
if err != nil {
t.Fatalf("Cannot read VM devices: %v", err)
}
v := l.SelectByType((*types.VirtualMachineVideoCard)(nil))
if len(v) != 1 {
t.Errorf("VM should have one video card")
}
if v[0].(*types.VirtualMachineVideoCard).VideoRamSizeInKB != 8192 {
t.Errorf("Video RAM should be equal 8192")
}
return nil
}
}
func TestCloneBuilderAcc_RAMReservation(t *testing.T) {
builderT.Test(t, builderT.TestCase{
Builder: &Builder{},
Template: RAMReservationConfig(),
Check: checkRAMReservation(t),
})
}
func RAMReservationConfig() string {
config := defaultConfig()
config["RAM_reserve_all"] = true
return commonT.RenderConfig(config)
}
func checkRAMReservation(t *testing.T) builderT.TestCheckFunc {
return func(artifacts []packer.Artifact) error {
d := commonT.TestConn(t)
vm := commonT.GetVM(t, d, artifacts)
vmInfo, err := vm.Info("config")
if err != nil {
t.Fatalf("Cannot read VM properties: %v", err)
}
if *vmInfo.Config.MemoryReservationLockedToMax != true {
t.Errorf("VM should have all RAM reserved")
}
return nil
}
}
func TestCloneBuilderAcc_sshPassword(t *testing.T) {
builderT.Test(t, builderT.TestCase{
Builder: &Builder{},
Template: sshPasswordConfig(),
Check: checkDefaultBootOrder(t),
})
}
func sshPasswordConfig() string {
config := defaultConfig()
config["communicator"] = "ssh"
config["ssh_username"] = "root"
config["ssh_password"] = "jetbrains"
return commonT.RenderConfig(config)
}
func checkDefaultBootOrder(t *testing.T) builderT.TestCheckFunc {
return func(artifacts []packer.Artifact) error {
d := commonT.TestConn(t)
vm := commonT.GetVM(t, d, artifacts)
vmInfo, err := vm.Info("config.bootOptions")
if err != nil {
t.Fatalf("Cannot read VM properties: %v", err)
}
order := vmInfo.Config.BootOptions.BootOrder
if order != nil {
t.Errorf("Boot order must be empty")
}
return nil
}
}
func TestCloneBuilderAcc_sshKey(t *testing.T) {
builderT.Test(t, builderT.TestCase{
Builder: &Builder{},
Template: sshKeyConfig(),
})
}
func sshKeyConfig() string {
config := defaultConfig()
config["communicator"] = "ssh"
config["ssh_username"] = "root"
config["ssh_private_key_file"] = "../test/test-key.pem"
return commonT.RenderConfig(config)
}
func TestCloneBuilderAcc_snapshot(t *testing.T) {
builderT.Test(t, builderT.TestCase{
Builder: &Builder{},
Template: snapshotConfig(),
Check: checkSnapshot(t),
})
}
func snapshotConfig() string {
config := defaultConfig()
config["linked_clone"] = false
config["create_snapshot"] = true
return commonT.RenderConfig(config)
}
func checkSnapshot(t *testing.T) builderT.TestCheckFunc {
return func(artifacts []packer.Artifact) error {
d := commonT.TestConn(t)
vm := commonT.GetVM(t, d, artifacts)
vmInfo, err := vm.Info("layoutEx.disk")
if err != nil {
t.Fatalf("Cannot read VM properties: %v", err)
}
layers := len(vmInfo.LayoutEx.Disk[0].Chain)
if layers != 2 {
t.Errorf("VM should have a single snapshot. expected 2 disk layers, got %v", layers)
}
return nil
}
}
func TestCloneBuilderAcc_template(t *testing.T) {
builderT.Test(t, builderT.TestCase{
Builder: &Builder{},
Template: templateConfig(),
Check: checkTemplate(t),
})
}
func templateConfig() string {
config := defaultConfig()
config["convert_to_template"] = true
return commonT.RenderConfig(config)
}
func checkTemplate(t *testing.T) builderT.TestCheckFunc {
return func(artifacts []packer.Artifact) error {
d := commonT.TestConn(t)
vm := commonT.GetVM(t, d, artifacts)
vmInfo, err := vm.Info("config.template")
if err != nil {
t.Fatalf("Cannot read VM properties: %v", err)
}
if vmInfo.Config.Template != true {
t.Error("Not a template")
}
return nil
}
}
func TestCloneBuilderAcc_bootOrder(t *testing.T) {
builderT.Test(t, builderT.TestCase{
Builder: &Builder{},
Template: bootOrderConfig(),
Check: checkBootOrder(t),
})
}
func bootOrderConfig() string {
config := defaultConfig()
config["communicator"] = "ssh"
config["ssh_username"] = "root"
config["ssh_password"] = "jetbrains"
config["boot_order"] = "disk,cdrom,floppy"
return commonT.RenderConfig(config)
}
func checkBootOrder(t *testing.T) builderT.TestCheckFunc {
return func(artifacts []packer.Artifact) error {
d := commonT.TestConn(t)
vm := commonT.GetVM(t, d, artifacts)
vmInfo, err := vm.Info("config.bootOptions")
if err != nil {
t.Fatalf("Cannot read VM properties: %v", err)
}
order := vmInfo.Config.BootOptions.BootOrder
if order == nil {
t.Errorf("Boot order must not be empty")
}
return nil
}
}
func TestCloneBuilderAcc_notes(t *testing.T) {
builderT.Test(t, builderT.TestCase{
Builder: &Builder{},
Template: notesConfig(),
Check: checkNotes(t),
})
}
func notesConfig() string {
config := defaultConfig()
config["notes"] = "test"
return commonT.RenderConfig(config)
}
func checkNotes(t *testing.T) builderT.TestCheckFunc {
return func(artifacts []packer.Artifact) error {
d := commonT.TestConn(t)
vm := commonT.GetVM(t, d, artifacts)
vmInfo, err := vm.Info("config.annotation")
if err != nil {
t.Fatalf("Cannot read VM properties: %v", err)
}
notes := vmInfo.Config.Annotation
if notes != "test" {
t.Errorf("notest should be 'test'")
}
return nil
}
}
func TestCloneBuilderAcc_windows(t *testing.T) {
t.Skip("test is too slow")
config := windowsConfig()
builderT.Test(t, builderT.TestCase{
Builder: &Builder{},
Template: commonT.RenderConfig(config),
})
}
func windowsConfig() map[string]interface{} {
username := os.Getenv("VSPHERE_USERNAME")
if username == "" {
username = "root"
}
password := os.Getenv("VSPHERE_PASSWORD")
if password == "" {
password = "jetbrains"
}
config := map[string]interface{}{
"vcenter_server": "vcenter.vsphere65.test",
"username": username,
"password": password,
"insecure_connection": true,
"vm_name": commonT.NewVMName(),
"template": "windows",
"host": "esxi-1.vsphere65.test",
"linked_clone": true, // speed up
"communicator": "winrm",
"winrm_username": "jetbrains",
"winrm_password": "jetbrains",
}
return config
}

View File

@ -0,0 +1,14 @@
package clone
import (
"github.com/hashicorp/packer/packer"
"testing"
)
func TestCloneBuilder_ImplementsBuilder(t *testing.T) {
var raw interface{}
raw = &Builder{}
if _, ok := raw.(packer.Builder); !ok {
t.Fatalf("Builder should be a builder")
}
}

View File

@ -0,0 +1,57 @@
package clone
import (
packerCommon "github.com/hashicorp/packer/common"
"github.com/hashicorp/packer/helper/communicator"
"github.com/hashicorp/packer/helper/config"
"github.com/hashicorp/packer/packer"
"github.com/hashicorp/packer/template/interpolate"
"github.com/jetbrains-infra/packer-builder-vsphere/common"
)
type Config struct {
packerCommon.PackerConfig `mapstructure:",squash"`
common.ConnectConfig `mapstructure:",squash"`
CloneConfig `mapstructure:",squash"`
common.LocationConfig `mapstructure:",squash"`
common.HardwareConfig `mapstructure:",squash"`
common.ConfigParamsConfig `mapstructure:",squash"`
common.RunConfig `mapstructure:",squash"`
common.WaitIpConfig `mapstructure:",squash"`
Comm communicator.Config `mapstructure:",squash"`
common.ShutdownConfig `mapstructure:",squash"`
CreateSnapshot bool `mapstructure:"create_snapshot"`
ConvertToTemplate bool `mapstructure:"convert_to_template"`
ctx interpolate.Context
}
func NewConfig(raws ...interface{}) (*Config, []string, error) {
c := new(Config)
err := config.Decode(c, &config.DecodeOpts{
Interpolate: true,
InterpolateContext: &c.ctx,
}, raws...)
if err != nil {
return nil, nil, err
}
errs := new(packer.MultiError)
errs = packer.MultiErrorAppend(errs, c.ConnectConfig.Prepare()...)
errs = packer.MultiErrorAppend(errs, c.CloneConfig.Prepare()...)
errs = packer.MultiErrorAppend(errs, c.LocationConfig.Prepare()...)
errs = packer.MultiErrorAppend(errs, c.HardwareConfig.Prepare()...)
errs = packer.MultiErrorAppend(errs, c.WaitIpConfig.Prepare()...)
errs = packer.MultiErrorAppend(errs, c.Comm.Prepare(&c.ctx)...)
errs = packer.MultiErrorAppend(errs, c.ShutdownConfig.Prepare()...)
if len(errs.Errors) > 0 {
return nil, nil, errs
}
return c, nil, nil
}

View File

@ -0,0 +1,70 @@
package clone
import (
"testing"
"time"
)
func TestCloneConfig_MinimalConfig(t *testing.T) {
_, warns, errs := NewConfig(minimalConfig())
testConfigOk(t, warns, errs)
}
func TestCloneConfig_MandatoryParameters(t *testing.T) {
params := []string{"vcenter_server", "username", "password", "template", "vm_name", "host"}
for _, param := range params {
raw := minimalConfig()
raw[param] = ""
_, warns, err := NewConfig(raw)
testConfigErr(t, param, warns, err)
}
}
func TestCloneConfig_Timeout(t *testing.T) {
raw := minimalConfig()
raw["shutdown_timeout"] = "3m"
conf, warns, err := NewConfig(raw)
testConfigOk(t, warns, err)
if conf.ShutdownConfig.Timeout != 3*time.Minute {
t.Fatalf("shutdown_timeout sould be equal 3 minutes, got %v", conf.ShutdownConfig.Timeout)
}
}
func TestCloneConfig_RAMReservation(t *testing.T) {
raw := minimalConfig()
raw["RAM_reservation"] = 1000
raw["RAM_reserve_all"] = true
_, warns, err := NewConfig(raw)
testConfigErr(t, "RAM_reservation", warns, err)
}
func minimalConfig() map[string]interface{} {
return map[string]interface{}{
"vcenter_server": "vcenter.domain.local",
"username": "root",
"password": "vmware",
"template": "ubuntu",
"vm_name": "vm1",
"host": "esxi1.domain.local",
"ssh_username": "root",
"ssh_password": "secret",
}
}
func testConfigOk(t *testing.T, warns []string, err error) {
if len(warns) > 0 {
t.Errorf("Should be no warnings: %#v", warns)
}
if err != nil {
t.Errorf("Unexpected error: %s", err)
}
}
func testConfigErr(t *testing.T, context string, warns []string, err error) {
if len(warns) > 0 {
t.Errorf("Should be no warnings: %#v", warns)
}
if err == nil {
t.Error("An error is not raised for", context)
}
}

View File

@ -0,0 +1,8 @@
package clone
import "testing"
import "go.uber.org/goleak"
func TestMain(m *testing.M) {
goleak.VerifyTestMain(m, goleak.IgnoreTopFunction("go.opencensus.io/stats/view.(*worker).start"))
}

View File

@ -0,0 +1,116 @@
package clone
import (
"context"
"fmt"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
"github.com/jetbrains-infra/packer-builder-vsphere/common"
"github.com/jetbrains-infra/packer-builder-vsphere/driver"
)
type CloneConfig struct {
Template string `mapstructure:"template"`
DiskSize int64 `mapstructure:"disk_size"`
LinkedClone bool `mapstructure:"linked_clone"`
Network string `mapstructure:"network"`
Notes string `mapstructure:"notes"`
}
func (c *CloneConfig) Prepare() []error {
var errs []error
if c.Template == "" {
errs = append(errs, fmt.Errorf("'template' is required"))
}
if c.LinkedClone == true && c.DiskSize != 0 {
errs = append(errs, fmt.Errorf("'linked_clone' and 'disk_size' cannot be used together"))
}
return errs
}
type StepCloneVM struct {
Config *CloneConfig
Location *common.LocationConfig
Force bool
}
func (s *StepCloneVM) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
ui := state.Get("ui").(packer.Ui)
d := state.Get("driver").(*driver.Driver)
vm, err := d.FindVM(s.Location.VMName)
if s.Force == false && err == nil {
state.Put("error", fmt.Errorf("%s already exists, you can use -force flag to destroy it", s.Location.VMName))
return multistep.ActionHalt
} else if s.Force == true && err == nil {
ui.Say(fmt.Sprintf("the vm/template %s already exists, but deleting it due to -force flag", s.Location.VMName))
err := vm.Destroy()
if err != nil {
state.Put("error", fmt.Errorf("error destroying %s: %v", s.Location.VMName, err))
}
}
ui.Say("Cloning VM...")
template, err := d.FindVM(s.Config.Template)
if err != nil {
state.Put("error", err)
return multistep.ActionHalt
}
vm, err = template.Clone(ctx, &driver.CloneConfig{
Name: s.Location.VMName,
Folder: s.Location.Folder,
Cluster: s.Location.Cluster,
Host: s.Location.Host,
ResourcePool: s.Location.ResourcePool,
Datastore: s.Location.Datastore,
LinkedClone: s.Config.LinkedClone,
Network: s.Config.Network,
Annotation: s.Config.Notes,
})
if err != nil {
state.Put("error", err)
return multistep.ActionHalt
}
if vm == nil {
return multistep.ActionHalt
}
state.Put("vm", vm)
if s.Config.DiskSize > 0 {
err = vm.ResizeDisk(s.Config.DiskSize)
if err != nil {
state.Put("error", err)
return multistep.ActionHalt
}
}
return multistep.ActionContinue
}
func (s *StepCloneVM) Cleanup(state multistep.StateBag) {
_, cancelled := state.GetOk(multistep.StateCancelled)
_, halted := state.GetOk(multistep.StateHalted)
if !cancelled && !halted {
return
}
ui := state.Get("ui").(packer.Ui)
st := state.Get("vm")
if st == nil {
return
}
vm := st.(*driver.VirtualMachine)
ui.Say("Destroying VM...")
err := vm.Destroy()
if err != nil {
ui.Error(err.Error())
}
}

View File

@ -0,0 +1,16 @@
package main
import "github.com/hashicorp/packer/packer/plugin"
import "github.com/jetbrains-infra/packer-builder-vsphere/clone"
func main() {
server, err := plugin.Server()
if err != nil {
panic(err)
}
err = server.RegisterBuilder(new(clone.Builder))
if err != nil {
panic(err)
}
server.Serve()
}

View File

@ -0,0 +1,16 @@
package main
import "github.com/hashicorp/packer/packer/plugin"
import "github.com/jetbrains-infra/packer-builder-vsphere/iso"
func main() {
server, err := plugin.Server()
if err != nil {
panic(err)
}
err = server.RegisterBuilder(new(iso.Builder))
if err != nil {
panic(err)
}
server.Serve()
}

View File

@ -0,0 +1,36 @@
package common
import (
"github.com/jetbrains-infra/packer-builder-vsphere/driver"
)
const BuilderId = "jetbrains.vsphere"
type Artifact struct {
Name string
VM *driver.VirtualMachine
}
func (a *Artifact) BuilderId() string {
return BuilderId
}
func (a *Artifact) Files() []string {
return []string{}
}
func (a *Artifact) Id() string {
return a.Name
}
func (a *Artifact) String() string {
return a.Name
}
func (a *Artifact) State(name string) interface{} {
return nil
}
func (a *Artifact) Destroy() error {
return a.VM.Destroy()
}

View File

@ -0,0 +1,25 @@
package common
import "fmt"
type LocationConfig struct {
VMName string `mapstructure:"vm_name"`
Folder string `mapstructure:"folder"`
Cluster string `mapstructure:"cluster"`
Host string `mapstructure:"host"`
ResourcePool string `mapstructure:"resource_pool"`
Datastore string `mapstructure:"datastore"`
}
func (c *LocationConfig) Prepare() []error {
var errs []error
if c.VMName == "" {
errs = append(errs, fmt.Errorf("'vm_name' is required"))
}
if c.Cluster == "" && c.Host == "" {
errs = append(errs, fmt.Errorf("'host' or 'cluster' is required"))
}
return errs
}

View File

@ -0,0 +1,15 @@
package common
import (
"github.com/hashicorp/packer/helper/multistep"
)
func CommHost(host string) func(multistep.StateBag) (string, error) {
return func(state multistep.StateBag) (string, error) {
if host != "" {
return host, nil
} else {
return state.Get("ip").(string), nil
}
}
}

View File

@ -0,0 +1,34 @@
package common
import (
"context"
"fmt"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
"github.com/jetbrains-infra/packer-builder-vsphere/driver"
)
type ConfigParamsConfig struct {
ConfigParams map[string]string `mapstructure:"configuration_parameters"`
}
type StepConfigParams struct {
Config *ConfigParamsConfig
}
func (s *StepConfigParams) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
ui := state.Get("ui").(packer.Ui)
vm := state.Get("vm").(*driver.VirtualMachine)
if s.Config.ConfigParams != nil {
ui.Say("Adding configuration parameters...")
if err := vm.AddConfigParams(s.Config.ConfigParams); err != nil {
state.Put("error", fmt.Errorf("error adding configuration parameters: %v", err))
return multistep.ActionHalt
}
}
return multistep.ActionContinue
}
func (s *StepConfigParams) Cleanup(state multistep.StateBag) {}

View File

@ -0,0 +1,55 @@
package common
import (
"context"
"fmt"
"github.com/hashicorp/packer/helper/multistep"
"github.com/jetbrains-infra/packer-builder-vsphere/driver"
)
type ConnectConfig struct {
VCenterServer string `mapstructure:"vcenter_server"`
Username string `mapstructure:"username"`
Password string `mapstructure:"password"`
InsecureConnection bool `mapstructure:"insecure_connection"`
Datacenter string `mapstructure:"datacenter"`
}
func (c *ConnectConfig) Prepare() []error {
var errs []error
if c.VCenterServer == "" {
errs = append(errs, fmt.Errorf("'vcenter_server' is required"))
}
if c.Username == "" {
errs = append(errs, fmt.Errorf("'username' is required"))
}
if c.Password == "" {
errs = append(errs, fmt.Errorf("'password' is required"))
}
return errs
}
type StepConnect struct {
Config *ConnectConfig
}
func (s *StepConnect) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
d, err := driver.NewDriver(&driver.ConnectConfig{
VCenterServer: s.Config.VCenterServer,
Username: s.Config.Username,
Password: s.Config.Password,
InsecureConnection: s.Config.InsecureConnection,
Datacenter: s.Config.Datacenter,
})
if err != nil {
state.Put("error", err)
return multistep.ActionHalt
}
state.Put("driver", d)
return multistep.ActionContinue
}
func (s *StepConnect) Cleanup(multistep.StateBag) {}

View File

@ -0,0 +1,70 @@
package common
import (
"context"
"fmt"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
"github.com/jetbrains-infra/packer-builder-vsphere/driver"
)
type HardwareConfig struct {
CPUs int32 `mapstructure:"CPUs"`
CpuCores int32 `mapstructure:"cpu_cores"`
CPUReservation int64 `mapstructure:"CPU_reservation"`
CPULimit int64 `mapstructure:"CPU_limit"`
CpuHotAddEnabled bool `mapstructure:"CPU_hot_plug"`
RAM int64 `mapstructure:"RAM"`
RAMReservation int64 `mapstructure:"RAM_reservation"`
RAMReserveAll bool `mapstructure:"RAM_reserve_all"`
MemoryHotAddEnabled bool `mapstructure:"RAM_hot_plug"`
VideoRAM int64 `mapstructure:"video_ram"`
NestedHV bool `mapstructure:"NestedHV"`
}
func (c *HardwareConfig) Prepare() []error {
var errs []error
if c.RAMReservation > 0 && c.RAMReserveAll != false {
errs = append(errs, fmt.Errorf("'RAM_reservation' and 'RAM_reserve_all' cannot be used together"))
}
return errs
}
type StepConfigureHardware struct {
Config *HardwareConfig
}
func (s *StepConfigureHardware) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
ui := state.Get("ui").(packer.Ui)
vm := state.Get("vm").(*driver.VirtualMachine)
if *s.Config != (HardwareConfig{}) {
ui.Say("Customizing hardware...")
err := vm.Configure(&driver.HardwareConfig{
CPUs: s.Config.CPUs,
CpuCores: s.Config.CpuCores,
CPUReservation: s.Config.CPUReservation,
CPULimit: s.Config.CPULimit,
RAM: s.Config.RAM,
RAMReservation: s.Config.RAMReservation,
RAMReserveAll: s.Config.RAMReserveAll,
NestedHV: s.Config.NestedHV,
CpuHotAddEnabled: s.Config.CpuHotAddEnabled,
MemoryHotAddEnabled: s.Config.MemoryHotAddEnabled,
VideoRAM: s.Config.VideoRAM,
})
if err != nil {
state.Put("error", err)
return multistep.ActionHalt
}
}
return multistep.ActionContinue
}
func (s *StepConfigureHardware) Cleanup(multistep.StateBag) {}

View File

@ -0,0 +1,75 @@
package common
import (
"context"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
"github.com/jetbrains-infra/packer-builder-vsphere/driver"
"strings"
)
type RunConfig struct {
BootOrder string `mapstructure:"boot_order"` // example: "floppy,cdrom,ethernet,disk"
}
type StepRun struct {
Config *RunConfig
SetOrder bool
}
func (s *StepRun) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
ui := state.Get("ui").(packer.Ui)
vm := state.Get("vm").(*driver.VirtualMachine)
if s.Config.BootOrder != "" {
ui.Say("Set boot order...")
order := strings.Split(s.Config.BootOrder, ",")
if err := vm.SetBootOrder(order); err != nil {
state.Put("error", err)
return multistep.ActionHalt
}
} else {
if s.SetOrder {
ui.Say("Set boot order temporary...")
if err := vm.SetBootOrder([]string{"disk", "cdrom"}); err != nil {
state.Put("error", err)
return multistep.ActionHalt
}
}
}
ui.Say("Power on VM...")
err := vm.PowerOn()
if err != nil {
state.Put("error", err)
return multistep.ActionHalt
}
return multistep.ActionContinue
}
func (s *StepRun) Cleanup(state multistep.StateBag) {
ui := state.Get("ui").(packer.Ui)
vm := state.Get("vm").(*driver.VirtualMachine)
if s.Config.BootOrder == "" && s.SetOrder {
ui.Say("Clear boot order...")
if err := vm.SetBootOrder([]string{"-"}); err != nil {
state.Put("error", err)
return
}
}
_, cancelled := state.GetOk(multistep.StateCancelled)
_, halted := state.GetOk(multistep.StateHalted)
if !cancelled && !halted {
return
}
ui.Say("Power off VM...")
err := vm.PowerOff()
if err != nil {
ui.Error(err.Error())
}
}

View File

@ -0,0 +1,73 @@
package common
import (
"bytes"
"context"
"fmt"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
"github.com/jetbrains-infra/packer-builder-vsphere/driver"
"log"
"time"
)
type ShutdownConfig struct {
Command string `mapstructure:"shutdown_command"`
Timeout time.Duration `mapstructure:"shutdown_timeout"`
}
func (c *ShutdownConfig) Prepare() []error {
var errs []error
if c.Timeout == 0 {
c.Timeout = 5 * time.Minute
}
return errs
}
type StepShutdown struct {
Config *ShutdownConfig
}
func (s *StepShutdown) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
ui := state.Get("ui").(packer.Ui)
comm := state.Get("communicator").(packer.Communicator)
vm := state.Get("vm").(*driver.VirtualMachine)
if s.Config.Command != "" {
ui.Say("Executing shutdown command...")
log.Printf("Shutdown command: %s", s.Config.Command)
var stdout, stderr bytes.Buffer
cmd := &packer.RemoteCmd{
Command: s.Config.Command,
Stdout: &stdout,
Stderr: &stderr,
}
err := comm.Start(ctx, cmd)
if err != nil {
state.Put("error", fmt.Errorf("Failed to send shutdown command: %s", err))
return multistep.ActionHalt
}
} else {
ui.Say("Shut down VM...")
err := vm.StartShutdown()
if err != nil {
state.Put("error", fmt.Errorf("Cannot shut down VM: %v", err))
return multistep.ActionHalt
}
}
log.Printf("Waiting max %s for shutdown to complete", s.Config.Timeout)
err := vm.WaitForShutdown(ctx, s.Config.Timeout)
if err != nil {
state.Put("error", err)
return multistep.ActionHalt
}
return multistep.ActionContinue
}
func (s *StepShutdown) Cleanup(state multistep.StateBag) {}

View File

@ -0,0 +1,31 @@
package common
import (
"context"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
"github.com/jetbrains-infra/packer-builder-vsphere/driver"
)
type StepCreateSnapshot struct {
CreateSnapshot bool
}
func (s *StepCreateSnapshot) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
ui := state.Get("ui").(packer.Ui)
vm := state.Get("vm").(*driver.VirtualMachine)
if s.CreateSnapshot {
ui.Say("Creating snapshot...")
err := vm.CreateSnapshot("Created by Packer")
if err != nil {
state.Put("error", err)
return multistep.ActionHalt
}
}
return multistep.ActionContinue
}
func (s *StepCreateSnapshot) Cleanup(state multistep.StateBag) {}

View File

@ -0,0 +1,30 @@
package common
import (
"context"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
"github.com/jetbrains-infra/packer-builder-vsphere/driver"
)
type StepConvertToTemplate struct {
ConvertToTemplate bool
}
func (s *StepConvertToTemplate) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
ui := state.Get("ui").(packer.Ui)
vm := state.Get("vm").(*driver.VirtualMachine)
if s.ConvertToTemplate {
ui.Say("Convert VM into template...")
err := vm.ConvertToTemplate()
if err != nil {
state.Put("error", err)
return multistep.ActionHalt
}
}
return multistep.ActionContinue
}
func (s *StepConvertToTemplate) Cleanup(state multistep.StateBag) {}

View File

@ -0,0 +1,129 @@
package common
import (
"context"
"fmt"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
"github.com/jetbrains-infra/packer-builder-vsphere/driver"
"log"
"time"
)
type WaitIpConfig struct {
WaitTimeout time.Duration `mapstructure:"ip_wait_timeout"`
SettleTimeout time.Duration `mapstructure:"ip_settle_timeout"`
// WaitTimeout is a total timeout, so even if VM changes IP frequently and it doesn't settle down we will end waiting.
}
type StepWaitForIp struct {
Config *WaitIpConfig
}
func (c *WaitIpConfig) Prepare() []error {
var errs []error
if c.SettleTimeout == 0 {
c.SettleTimeout = 5 * time.Second
}
if c.WaitTimeout == 0 {
c.WaitTimeout = 30 * time.Minute
}
return errs
}
func (s *StepWaitForIp) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
ui := state.Get("ui").(packer.Ui)
vm := state.Get("vm").(*driver.VirtualMachine)
var ip string
var err error
sub, cancel := context.WithCancel(ctx)
waitDone := make(chan bool, 1)
defer func() {
cancel()
}()
go func() {
ui.Say("Waiting for IP...")
ip, err = doGetIp(vm, sub, s.Config)
waitDone <- true
}()
log.Printf("[INFO] Waiting for IP, up to total timeout: %s, settle timeout: %s", s.Config.WaitTimeout, s.Config.SettleTimeout)
timeout := time.After(s.Config.WaitTimeout)
for {
select {
case <-timeout:
err := fmt.Errorf("Timeout waiting for IP.")
state.Put("error", err)
ui.Error(err.Error())
cancel()
return multistep.ActionHalt
case <-ctx.Done():
cancel()
log.Println("[WARN] Interrupt detected, quitting waiting for IP.")
return multistep.ActionHalt
case <-waitDone:
if err != nil {
state.Put("error", err)
return multistep.ActionHalt
}
state.Put("ip", ip)
ui.Say(fmt.Sprintf("IP address: %v", ip))
return multistep.ActionContinue
case <-time.After(1 * time.Second):
if _, ok := state.GetOk(multistep.StateCancelled); ok {
return multistep.ActionHalt
}
}
}
}
func doGetIp(vm *driver.VirtualMachine, ctx context.Context, c *WaitIpConfig) (string, error) {
var prevIp = ""
var stopTime time.Time
var interval time.Duration
if c.SettleTimeout.Seconds() >= 120 {
interval = 30 * time.Second
} else if c.SettleTimeout.Seconds() >= 60 {
interval = 15 * time.Second
} else if c.SettleTimeout.Seconds() >= 10 {
interval = 5 * time.Second
} else {
interval = 1 * time.Second
}
loop:
ip, err := vm.WaitForIP(ctx)
if err != nil {
return "", err
}
if prevIp == "" || prevIp != ip {
if prevIp == "" {
log.Printf("VM IP aquired: %s", ip)
} else {
log.Printf("VM IP changed from %s to %s", prevIp, ip)
}
prevIp = ip
stopTime = time.Now().Add(c.SettleTimeout)
goto loop
} else {
log.Printf("VM IP is still the same: %s", prevIp)
if time.Now().After(stopTime) {
log.Printf("VM IP seems stable enough: %s", ip)
return ip, nil
}
select {
case <-ctx.Done():
return "", fmt.Errorf("IP wait cancelled")
case <-time.After(interval):
goto loop
}
}
}
func (s *StepWaitForIp) Cleanup(state multistep.StateBag) {}

View File

@ -0,0 +1,68 @@
package testing
import (
"encoding/json"
"fmt"
"github.com/hashicorp/packer/packer"
"github.com/jetbrains-infra/packer-builder-vsphere/common"
"github.com/jetbrains-infra/packer-builder-vsphere/driver"
"math/rand"
"os"
"testing"
"time"
)
func NewVMName() string {
rand.Seed(time.Now().UnixNano())
return fmt.Sprintf("test-%v", rand.Intn(1000))
}
func RenderConfig(config map[string]interface{}) string {
t := map[string][]map[string]interface{}{
"builders": {
map[string]interface{}{
"type": "test",
},
},
}
for k, v := range config {
t["builders"][0][k] = v
}
j, _ := json.Marshal(t)
return string(j)
}
func TestConn(t *testing.T) *driver.Driver {
username := os.Getenv("VSPHERE_USERNAME")
if username == "" {
username = "root"
}
password := os.Getenv("VSPHERE_PASSWORD")
if password == "" {
password = "jetbrains"
}
d, err := driver.NewDriver(&driver.ConnectConfig{
VCenterServer: "vcenter.vsphere65.test",
Username: username,
Password: password,
InsecureConnection: true,
})
if err != nil {
t.Fatal("Cannot connect: ", err)
}
return d
}
func GetVM(t *testing.T, d *driver.Driver, artifacts []packer.Artifact) *driver.VirtualMachine {
artifactRaw := artifacts[0]
artifact, _ := artifactRaw.(*common.Artifact)
vm, err := d.FindVM(artifact.Name)
if err != nil {
t.Fatalf("Cannot find VM: %v", err)
}
return vm
}

View File

@ -0,0 +1,27 @@
version: '2'
services:
build:
image: jetbrainsinfra/golang:1.11.4
volumes:
- .:/work
- modules:/go/pkg/mod
- cache:/root/.cache
working_dir: /work
command: make build -j 3
test:
image: jetbrainsinfra/golang:1.11.4
volumes:
- .:/work
- modules:/go/pkg/mod
- cache:/root/.cache
working_dir: /work
# network_mode: "container:vpn"
environment:
VSPHERE_USERNAME:
VSPHERE_PASSWORD:
command: make test
volumes:
modules:
cache:

View File

@ -0,0 +1,129 @@
package driver
import (
"fmt"
"github.com/vmware/govmomi/object"
"github.com/vmware/govmomi/vim25/mo"
"github.com/vmware/govmomi/vim25/soap"
"github.com/vmware/govmomi/vim25/types"
)
type Datastore struct {
ds *object.Datastore
driver *Driver
}
func (d *Driver) NewDatastore(ref *types.ManagedObjectReference) *Datastore {
return &Datastore{
ds: object.NewDatastore(d.client.Client, *ref),
driver: d,
}
}
// If name is an empty string, then resolve host's one
func (d *Driver) FindDatastore(name string, host string) (*Datastore, error) {
if name == "" {
h, err := d.FindHost(host)
if err != nil {
return nil, err
}
i, err := h.Info("datastore")
if err != nil {
return nil, err
}
if len(i.Datastore) > 1 {
return nil, fmt.Errorf("Host has multiple datastores. Specify it explicitly")
}
ds := d.NewDatastore(&i.Datastore[0])
inf, err := ds.Info("name")
if err != nil {
return nil, err
}
name = inf.Name
}
ds, err := d.finder.Datastore(d.ctx, name)
if err != nil {
return nil, err
}
return &Datastore{
ds: ds,
driver: d,
}, nil
}
func (ds *Datastore) Info(params ...string) (*mo.Datastore, error) {
var p []string
if len(params) == 0 {
p = []string{"*"}
} else {
p = params
}
var info mo.Datastore
err := ds.ds.Properties(ds.driver.ctx, ds.ds.Reference(), p, &info)
if err != nil {
return nil, err
}
return &info, nil
}
func (ds *Datastore) FileExists(path string) bool {
_, err := ds.ds.Stat(ds.driver.ctx, path)
return err == nil
}
func (ds *Datastore) Name() string {
return ds.ds.Name()
}
func (ds *Datastore) ResolvePath(path string) string {
return ds.ds.Path(path)
}
func (ds *Datastore) UploadFile(src, dst string, host string) error {
p := soap.DefaultUpload
ctx := ds.driver.ctx
if host != "" {
h, err := ds.driver.FindHost(host)
if err != nil {
return err
}
ctx = ds.ds.HostContext(ctx, h.host)
}
return ds.ds.UploadFile(ctx, src, dst, &p)
}
func (ds *Datastore) Delete(path string) error {
dc, err := ds.driver.finder.Datacenter(ds.driver.ctx, ds.ds.DatacenterPath)
if err != nil {
return err
}
fm := ds.ds.NewFileManager(dc, false)
return fm.Delete(ds.driver.ctx, path)
}
func (ds *Datastore) MakeDirectory(path string) error {
dc, err := ds.driver.finder.Datacenter(ds.driver.ctx, ds.ds.DatacenterPath)
if err != nil {
return err
}
fm := ds.ds.NewFileManager(dc, false)
return fm.FileManager.MakeDirectory(ds.driver.ctx, path, dc, true)
}
// Cuts out the datastore prefix
// Example: "[datastore1] file.ext" --> "file.ext"
func RemoveDatastorePrefix(path string) string {
res := object.DatastorePath{}
if hadPrefix := res.FromString(path); hadPrefix {
return res.Path
} else {
return path
}
}

View File

@ -0,0 +1,93 @@
package driver
import (
"fmt"
"io/ioutil"
"testing"
"time"
)
func TestDatastoreAcc(t *testing.T) {
d := newTestDriver(t)
ds, err := d.FindDatastore("datastore1", "")
if err != nil {
t.Fatalf("Cannot find the default datastore '%v': %v", "datastore1", err)
}
info, err := ds.Info("name")
if err != nil {
t.Fatalf("Cannot read datastore properties: %v", err)
}
if info.Name != "datastore1" {
t.Errorf("Wrong datastore. expected: 'datastore1', got: '%v'", info.Name)
}
}
func TestFileUpload(t *testing.T) {
dsName := "datastore1"
hostName := "esxi-1.vsphere65.test"
fileName := fmt.Sprintf("test-%v", time.Now().Unix())
tmpFile, err := ioutil.TempFile("", fileName)
if err != nil {
t.Fatalf("Error creating temp file")
}
err = tmpFile.Close()
if err != nil {
t.Fatalf("Error creating temp file")
}
d := newTestDriver(t)
ds, err := d.FindDatastore(dsName, hostName)
if err != nil {
t.Fatalf("Cannot find datastore '%v': %v", dsName, err)
}
err = ds.UploadFile(tmpFile.Name(), fileName, hostName)
if err != nil {
t.Fatalf("Cannot upload file: %v", err)
}
if ds.FileExists(fileName) != true {
t.Fatalf("Cannot find file")
}
err = ds.Delete(fileName)
if err != nil {
t.Fatalf("Cannot delete file: %v", err)
}
}
func TestFileUploadDRS(t *testing.T) {
dsName := "datastore3"
hostName := ""
fileName := fmt.Sprintf("test-%v", time.Now().Unix())
tmpFile, err := ioutil.TempFile("", fileName)
if err != nil {
t.Fatalf("Error creating temp file")
}
err = tmpFile.Close()
if err != nil {
t.Fatalf("Error creating temp file")
}
d := newTestDriver(t)
ds, err := d.FindDatastore(dsName, hostName)
if err != nil {
t.Fatalf("Cannot find datastore '%v': %v", dsName, err)
}
err = ds.UploadFile(tmpFile.Name(), fileName, hostName)
if err != nil {
t.Fatalf("Cannot upload file: %v", err)
}
if ds.FileExists(fileName) != true {
t.Fatalf("Cannot find file")
}
err = ds.Delete(fileName)
if err != nil {
t.Fatalf("Cannot delete file: %v", err)
}
}

View File

@ -0,0 +1,72 @@
package driver
import (
"context"
"fmt"
"github.com/vmware/govmomi"
"github.com/vmware/govmomi/find"
"github.com/vmware/govmomi/object"
"github.com/vmware/govmomi/session"
"github.com/vmware/govmomi/vim25"
"github.com/vmware/govmomi/vim25/soap"
"net/url"
"time"
)
type Driver struct {
ctx context.Context
client *govmomi.Client
finder *find.Finder
datacenter *object.Datacenter
}
type ConnectConfig struct {
VCenterServer string
Username string
Password string
InsecureConnection bool
Datacenter string
}
func NewDriver(config *ConnectConfig) (*Driver, error) {
ctx := context.TODO()
vcenterUrl, err := url.Parse(fmt.Sprintf("https://%v/sdk", config.VCenterServer))
if err != nil {
return nil, err
}
credentials := url.UserPassword(config.Username, config.Password)
vcenterUrl.User = credentials
soapClient := soap.NewClient(vcenterUrl, config.InsecureConnection)
vimClient, err := vim25.NewClient(ctx, soapClient)
if err != nil {
return nil, err
}
vimClient.RoundTripper = session.KeepAlive(vimClient.RoundTripper, 10*time.Minute)
client := &govmomi.Client{
Client: vimClient,
SessionManager: session.NewManager(vimClient),
}
err = client.SessionManager.Login(ctx, credentials)
if err != nil {
return nil, err
}
finder := find.NewFinder(client.Client, false)
datacenter, err := finder.DatacenterOrDefault(ctx, config.Datacenter)
if err != nil {
return nil, err
}
finder.SetDatacenter(datacenter)
d := Driver{
ctx: ctx,
client: client,
datacenter: datacenter,
finder: finder,
}
return &d, nil
}

View File

@ -0,0 +1,39 @@
package driver
import (
"fmt"
"math/rand"
"os"
"testing"
"time"
)
// Defines whether acceptance tests should be run
const TestHostName = "esxi-1.vsphere65.test"
func newTestDriver(t *testing.T) *Driver {
username := os.Getenv("VSPHERE_USERNAME")
if username == "" {
username = "root"
}
password := os.Getenv("VSPHERE_PASSWORD")
if password == "" {
password = "jetbrains"
}
d, err := NewDriver(&ConnectConfig{
VCenterServer: "vcenter.vsphere65.test",
Username: username,
Password: password,
InsecureConnection: true,
})
if err != nil {
t.Fatalf("Cannot connect: %v", err)
}
return d
}
func newVMName() string {
rand.Seed(time.Now().UTC().UnixNano())
return fmt.Sprintf("test-%v", rand.Intn(1000))
}

View File

@ -0,0 +1,67 @@
package driver
import (
"fmt"
"github.com/vmware/govmomi/object"
"github.com/vmware/govmomi/vim25/mo"
"github.com/vmware/govmomi/vim25/types"
)
type Folder struct {
driver *Driver
folder *object.Folder
}
func (d *Driver) NewFolder(ref *types.ManagedObjectReference) *Folder {
return &Folder{
folder: object.NewFolder(d.client.Client, *ref),
driver: d,
}
}
func (d *Driver) FindFolder(name string) (*Folder, error) {
f, err := d.finder.Folder(d.ctx, fmt.Sprintf("/%v/vm/%v", d.datacenter.Name(), name))
if err != nil {
return nil, err
}
return &Folder{
folder: f,
driver: d,
}, nil
}
func (f *Folder) Info(params ...string) (*mo.Folder, error) {
var p []string
if len(params) == 0 {
p = []string{"*"}
} else {
p = params
}
var info mo.Folder
err := f.folder.Properties(f.driver.ctx, f.folder.Reference(), p, &info)
if err != nil {
return nil, err
}
return &info, nil
}
func (f *Folder) Path() (string, error) {
info, err := f.Info("name", "parent")
if err != nil {
return "", err
}
if info.Parent.Type == "Datacenter" {
return "", nil
} else {
parent := f.driver.NewFolder(info.Parent)
path, err := parent.Path()
if err != nil {
return "", err
}
if path == "" {
return info.Name, nil
} else {
return fmt.Sprintf("%v/%v", path, info.Name), nil
}
}
}

View File

@ -0,0 +1,18 @@
package driver
import "testing"
func TestFolderAcc(t *testing.T) {
d := newTestDriver(t)
f, err := d.FindFolder("folder1/folder2")
if err != nil {
t.Fatalf("Cannot find the default folder '%v': %v", "folder1/folder2", err)
}
path, err := f.Path()
if err != nil {
t.Fatalf("Cannot read folder name: %v", err)
}
if path != "folder1/folder2" {
t.Errorf("Wrong folder. expected: 'folder1/folder2', got: '%v'", path)
}
}

View File

@ -0,0 +1,45 @@
package driver
import (
"github.com/vmware/govmomi/object"
"github.com/vmware/govmomi/vim25/mo"
"github.com/vmware/govmomi/vim25/types"
)
type Host struct {
driver *Driver
host *object.HostSystem
}
func (d *Driver) NewHost(ref *types.ManagedObjectReference) *Host {
return &Host{
host: object.NewHostSystem(d.client.Client, *ref),
driver: d,
}
}
func (d *Driver) FindHost(name string) (*Host, error) {
h, err := d.finder.HostSystem(d.ctx, name)
if err != nil {
return nil, err
}
return &Host{
host: h,
driver: d,
}, nil
}
func (h *Host) Info(params ...string) (*mo.HostSystem, error) {
var p []string
if len(params) == 0 {
p = []string{"*"}
} else {
p = params
}
var info mo.HostSystem
err := h.host.Properties(h.driver.ctx, h.host.Reference(), p, &info)
if err != nil {
return nil, err
}
return &info, nil
}

View File

@ -0,0 +1,21 @@
package driver
import (
"testing"
)
func TestHostAcc(t *testing.T) {
d := newTestDriver(t)
host, err := d.FindHost(TestHostName)
if err != nil {
t.Fatalf("Cannot find the default host '%v': %v", "datastore1", err)
}
info, err := host.Info("name")
if err != nil {
t.Fatalf("Cannot read host properties: %v", err)
}
if info.Name != TestHostName {
t.Errorf("Wrong host name: expected '%v', got: '%v'", TestHostName, info.Name)
}
}

View File

@ -0,0 +1,8 @@
package driver
import "testing"
import "go.uber.org/goleak"
func TestMain(m *testing.M) {
goleak.VerifyTestMain(m, goleak.IgnoreTopFunction("go.opencensus.io/stats/view.(*worker).start"))
}

View File

@ -0,0 +1,45 @@
package driver
import (
"github.com/vmware/govmomi/object"
"github.com/vmware/govmomi/vim25/mo"
"github.com/vmware/govmomi/vim25/types"
)
type Network struct {
driver *Driver
network *object.Network
}
func (d *Driver) NewNetwork(ref *types.ManagedObjectReference) *Network {
return &Network{
network: object.NewNetwork(d.client.Client, *ref),
driver: d,
}
}
func (d *Driver) FindNetwork(name string) (*Network, error) {
n, err := d.finder.Network(d.ctx, name)
if err != nil {
return nil, err
}
return &Network{
network: n.(*object.Network),
driver: d,
}, nil
}
func (n *Network) Info(params ...string) (*mo.Network, error) {
var p []string
if len(params) == 0 {
p = []string{"*"}
} else {
p = params
}
var info mo.Network
err := n.network.Properties(n.driver.ctx, n.network.Reference(), p, &info)
if err != nil {
return nil, err
}
return &info, nil
}

View File

@ -0,0 +1,74 @@
package driver
import (
"fmt"
"github.com/vmware/govmomi/object"
"github.com/vmware/govmomi/vim25/mo"
"github.com/vmware/govmomi/vim25/types"
)
type ResourcePool struct {
pool *object.ResourcePool
driver *Driver
}
func (d *Driver) NewResourcePool(ref *types.ManagedObjectReference) *ResourcePool {
return &ResourcePool{
pool: object.NewResourcePool(d.client.Client, *ref),
driver: d,
}
}
func (d *Driver) FindResourcePool(cluster string, host string, name string) (*ResourcePool, error) {
var res string
if cluster != "" {
res = cluster
} else {
res = host
}
p, err := d.finder.ResourcePool(d.ctx, fmt.Sprintf("%v/Resources/%v", res, name))
if err != nil {
return nil, err
}
return &ResourcePool{
pool: p,
driver: d,
}, nil
}
func (p *ResourcePool) Info(params ...string) (*mo.ResourcePool, error) {
var params2 []string
if len(params) == 0 {
params2 = []string{"*"}
} else {
params2 = params
}
var info mo.ResourcePool
err := p.pool.Properties(p.driver.ctx, p.pool.Reference(), params2, &info)
if err != nil {
return nil, err
}
return &info, nil
}
func (p *ResourcePool) Path() (string, error) {
poolInfo, err := p.Info("name", "parent")
if err != nil {
return "", err
}
if poolInfo.Parent.Type == "ComputeResource" {
return "", nil
} else {
parent := p.driver.NewResourcePool(poolInfo.Parent)
parentPath, err := parent.Path()
if err != nil {
return "", err
}
if parentPath == "" {
return poolInfo.Name, nil
} else {
return fmt.Sprintf("%v/%v", parentPath, poolInfo.Name), nil
}
}
}

View File

@ -0,0 +1,19 @@
package driver
import "testing"
func TestResourcePoolAcc(t *testing.T) {
d := newTestDriver(t)
p, err := d.FindResourcePool("", "esxi-1.vsphere65.test", "pool1/pool2")
if err != nil {
t.Fatalf("Cannot find the default resource pool '%v': %v", "pool1/pool2", err)
}
path, err := p.Path()
if err != nil {
t.Fatalf("Cannot read resource pool name: %v", err)
}
if path != "pool1/pool2" {
t.Errorf("Wrong folder. expected: 'pool1/pool2', got: '%v'", path)
}
}

View File

@ -0,0 +1,665 @@
package driver
import (
"context"
"errors"
"fmt"
"github.com/vmware/govmomi/object"
"github.com/vmware/govmomi/vim25/mo"
"github.com/vmware/govmomi/vim25/types"
"log"
"strings"
"time"
)
type VirtualMachine struct {
vm *object.VirtualMachine
driver *Driver
}
type CloneConfig struct {
Name string
Folder string
Cluster string
Host string
ResourcePool string
Datastore string
LinkedClone bool
Network string
Annotation string
}
type HardwareConfig struct {
CPUs int32
CpuCores int32
CPUReservation int64
CPULimit int64
RAM int64
RAMReservation int64
RAMReserveAll bool
NestedHV bool
CpuHotAddEnabled bool
MemoryHotAddEnabled bool
VideoRAM int64
}
type CreateConfig struct {
DiskThinProvisioned bool
DiskControllerType string // example: "scsi", "pvscsi"
DiskSize int64
Annotation string
Name string
Folder string
Cluster string
Host string
ResourcePool string
Datastore string
GuestOS string // example: otherGuest
Network string // "" for default network
NetworkCard string // example: vmxnet3
USBController bool
Version uint // example: 10
Firmware string // efi or bios
}
func (d *Driver) NewVM(ref *types.ManagedObjectReference) *VirtualMachine {
return &VirtualMachine{
vm: object.NewVirtualMachine(d.client.Client, *ref),
driver: d,
}
}
func (d *Driver) FindVM(name string) (*VirtualMachine, error) {
vm, err := d.finder.VirtualMachine(d.ctx, name)
if err != nil {
return nil, err
}
return &VirtualMachine{
vm: vm,
driver: d,
}, nil
}
func (d *Driver) CreateVM(config *CreateConfig) (*VirtualMachine, error) {
createSpec := types.VirtualMachineConfigSpec{
Name: config.Name,
Annotation: config.Annotation,
GuestId: config.GuestOS,
}
if config.Version != 0 {
createSpec.Version = fmt.Sprintf("%s%d", "vmx-", config.Version)
}
if config.Firmware != "" {
createSpec.Firmware = config.Firmware
}
folder, err := d.FindFolder(config.Folder)
if err != nil {
return nil, err
}
resourcePool, err := d.FindResourcePool(config.Cluster, config.Host, config.ResourcePool)
if err != nil {
return nil, err
}
var host *object.HostSystem
if config.Cluster != "" && config.Host != "" {
h, err := d.FindHost(config.Host)
if err != nil {
return nil, err
}
host = h.host
}
datastore, err := d.FindDatastore(config.Datastore, config.Host)
if err != nil {
return nil, err
}
devices := object.VirtualDeviceList{}
devices, err = addDisk(d, devices, config)
if err != nil {
return nil, err
}
devices, err = addNetwork(d, devices, config)
if err != nil {
return nil, err
}
if config.USBController {
t := true
usb := &types.VirtualUSBController{
EhciEnabled: &t,
}
devices = append(devices, usb)
}
createSpec.DeviceChange, err = devices.ConfigSpec(types.VirtualDeviceConfigSpecOperationAdd)
if err != nil {
return nil, err
}
createSpec.Files = &types.VirtualMachineFileInfo{
VmPathName: fmt.Sprintf("[%s]", datastore.Name()),
}
task, err := folder.folder.CreateVM(d.ctx, createSpec, resourcePool.pool, host)
if err != nil {
return nil, err
}
taskInfo, err := task.WaitForResult(d.ctx, nil)
if err != nil {
return nil, err
}
vmRef := taskInfo.Result.(types.ManagedObjectReference)
return d.NewVM(&vmRef), nil
}
func (vm *VirtualMachine) Info(params ...string) (*mo.VirtualMachine, error) {
var p []string
if len(params) == 0 {
p = []string{"*"}
} else {
p = params
}
var info mo.VirtualMachine
err := vm.vm.Properties(vm.driver.ctx, vm.vm.Reference(), p, &info)
if err != nil {
return nil, err
}
return &info, nil
}
func (vm *VirtualMachine) Devices() (object.VirtualDeviceList, error) {
vmInfo, err := vm.Info("config.hardware.device")
if err != nil {
return nil, err
}
return vmInfo.Config.Hardware.Device, nil
}
func (vm *VirtualMachine) Clone(ctx context.Context, config *CloneConfig) (*VirtualMachine, error) {
folder, err := vm.driver.FindFolder(config.Folder)
if err != nil {
return nil, err
}
var relocateSpec types.VirtualMachineRelocateSpec
pool, err := vm.driver.FindResourcePool(config.Cluster, config.Host, config.ResourcePool)
if err != nil {
return nil, err
}
poolRef := pool.pool.Reference()
relocateSpec.Pool = &poolRef
datastore, err := vm.driver.FindDatastore(config.Datastore, config.Host)
if err != nil {
return nil, err
}
datastoreRef := datastore.ds.Reference()
relocateSpec.Datastore = &datastoreRef
var cloneSpec types.VirtualMachineCloneSpec
cloneSpec.Location = relocateSpec
cloneSpec.PowerOn = false
if config.LinkedClone == true {
cloneSpec.Location.DiskMoveType = "createNewChildDiskBacking"
tpl, err := vm.Info("snapshot")
if err != nil {
return nil, err
}
if tpl.Snapshot == nil {
err = errors.New("`linked_clone=true`, but template has no snapshots")
return nil, err
}
cloneSpec.Snapshot = tpl.Snapshot.CurrentSnapshot
}
var configSpec types.VirtualMachineConfigSpec
cloneSpec.Config = &configSpec
if config.Annotation != "" {
configSpec.Annotation = config.Annotation
}
if config.Network != "" {
net, err := vm.driver.FindNetwork(config.Network)
if err != nil {
return nil, err
}
backing, err := net.network.EthernetCardBackingInfo(ctx)
if err != nil {
return nil, err
}
devices, err := vm.vm.Device(ctx)
if err != nil {
return nil, err
}
adapter, err := findNetworkAdapter(devices)
if err != nil {
return nil, err
}
adapter.GetVirtualEthernetCard().Backing = backing
config := &types.VirtualDeviceConfigSpec{
Device: adapter.(types.BaseVirtualDevice),
Operation: types.VirtualDeviceConfigSpecOperationEdit,
}
configSpec.DeviceChange = append(configSpec.DeviceChange, config)
}
task, err := vm.vm.Clone(vm.driver.ctx, folder.folder, config.Name, cloneSpec)
if err != nil {
return nil, err
}
info, err := task.WaitForResult(ctx, nil)
if err != nil {
if ctx.Err() == context.Canceled {
err = task.Cancel(context.TODO())
return nil, err
}
return nil, err
}
vmRef := info.Result.(types.ManagedObjectReference)
created := vm.driver.NewVM(&vmRef)
return created, nil
}
func (vm *VirtualMachine) Destroy() error {
task, err := vm.vm.Destroy(vm.driver.ctx)
if err != nil {
return err
}
_, err = task.WaitForResult(vm.driver.ctx, nil)
return err
}
func (vm *VirtualMachine) Configure(config *HardwareConfig) error {
var confSpec types.VirtualMachineConfigSpec
confSpec.NumCPUs = config.CPUs
confSpec.NumCoresPerSocket = config.CpuCores
confSpec.MemoryMB = config.RAM
var cpuSpec types.ResourceAllocationInfo
cpuSpec.Reservation = &config.CPUReservation
if config.CPULimit != 0 {
cpuSpec.Limit = &config.CPULimit
}
confSpec.CpuAllocation = &cpuSpec
var ramSpec types.ResourceAllocationInfo
ramSpec.Reservation = &config.RAMReservation
confSpec.MemoryAllocation = &ramSpec
confSpec.MemoryReservationLockedToMax = &config.RAMReserveAll
confSpec.NestedHVEnabled = &config.NestedHV
confSpec.CpuHotAddEnabled = &config.CpuHotAddEnabled
confSpec.MemoryHotAddEnabled = &config.MemoryHotAddEnabled
if config.VideoRAM != 0 {
devices, err := vm.vm.Device(vm.driver.ctx)
if err != nil {
return err
}
l := devices.SelectByType((*types.VirtualMachineVideoCard)(nil))
if len(l) != 1 {
return err
}
card := l[0].(*types.VirtualMachineVideoCard)
card.VideoRamSizeInKB = config.VideoRAM
spec := &types.VirtualDeviceConfigSpec{
Device: card,
Operation: types.VirtualDeviceConfigSpecOperationEdit,
}
confSpec.DeviceChange = append(confSpec.DeviceChange, spec)
}
task, err := vm.vm.Reconfigure(vm.driver.ctx, confSpec)
if err != nil {
return err
}
_, err = task.WaitForResult(vm.driver.ctx, nil)
return err
}
func (vm *VirtualMachine) ResizeDisk(diskSize int64) error {
var confSpec types.VirtualMachineConfigSpec
devices, err := vm.vm.Device(vm.driver.ctx)
if err != nil {
return err
}
disk, err := findDisk(devices)
if err != nil {
return err
}
disk.CapacityInKB = diskSize * 1024
confSpec.DeviceChange = []types.BaseVirtualDeviceConfigSpec{
&types.VirtualDeviceConfigSpec{
Device: disk,
Operation: types.VirtualDeviceConfigSpecOperationEdit,
},
}
task, err := vm.vm.Reconfigure(vm.driver.ctx, confSpec)
if err != nil {
return err
}
_, err = task.WaitForResult(vm.driver.ctx, nil)
return err
}
func findDisk(devices object.VirtualDeviceList) (*types.VirtualDisk, error) {
var disks []*types.VirtualDisk
for _, device := range devices {
switch d := device.(type) {
case *types.VirtualDisk:
disks = append(disks, d)
}
}
switch len(disks) {
case 0:
return nil, errors.New("VM has no disks")
case 1:
return disks[0], nil
}
return nil, errors.New("VM has multiple disks")
}
func (vm *VirtualMachine) PowerOn() error {
task, err := vm.vm.PowerOn(vm.driver.ctx)
if err != nil {
return err
}
_, err = task.WaitForResult(vm.driver.ctx, nil)
return err
}
func (vm *VirtualMachine) WaitForIP(ctx context.Context) (string, error) {
return vm.vm.WaitForIP(ctx)
}
func (vm *VirtualMachine) PowerOff() error {
state, err := vm.vm.PowerState(vm.driver.ctx)
if err != nil {
return err
}
if state == types.VirtualMachinePowerStatePoweredOff {
return nil
}
task, err := vm.vm.PowerOff(vm.driver.ctx)
if err != nil {
return err
}
_, err = task.WaitForResult(vm.driver.ctx, nil)
return err
}
func (vm *VirtualMachine) StartShutdown() error {
err := vm.vm.ShutdownGuest(vm.driver.ctx)
return err
}
func (vm *VirtualMachine) WaitForShutdown(ctx context.Context, timeout time.Duration) error {
shutdownTimer := time.After(timeout)
for {
powerState, err := vm.vm.PowerState(vm.driver.ctx)
if err != nil {
return err
}
if powerState == "poweredOff" {
break
}
select {
case <-shutdownTimer:
err := errors.New("Timeout while waiting for machine to shut down.")
return err
case <-ctx.Done():
return nil
default:
time.Sleep(1 * time.Second)
}
}
return nil
}
func (vm *VirtualMachine) CreateSnapshot(name string) error {
task, err := vm.vm.CreateSnapshot(vm.driver.ctx, name, "", false, false)
if err != nil {
return err
}
_, err = task.WaitForResult(vm.driver.ctx, nil)
return err
}
func (vm *VirtualMachine) ConvertToTemplate() error {
return vm.vm.MarkAsTemplate(vm.driver.ctx)
}
func (vm *VirtualMachine) GetDir() (string, error) {
vmInfo, err := vm.Info("name", "layoutEx.file")
if err != nil {
return "", err
}
vmxName := fmt.Sprintf("/%s.vmx", vmInfo.Name)
for _, file := range vmInfo.LayoutEx.File {
if strings.HasSuffix(file.Name, vmxName) {
return RemoveDatastorePrefix(file.Name[:len(file.Name)-len(vmxName)]), nil
}
}
return "", fmt.Errorf("cannot find '%s'", vmxName)
}
func addDisk(_ *Driver, devices object.VirtualDeviceList, config *CreateConfig) (object.VirtualDeviceList, error) {
device, err := devices.CreateSCSIController(config.DiskControllerType)
if err != nil {
return nil, err
}
devices = append(devices, device)
controller, err := devices.FindDiskController(devices.Name(device))
if err != nil {
return nil, err
}
disk := &types.VirtualDisk{
VirtualDevice: types.VirtualDevice{
Key: devices.NewKey(),
Backing: &types.VirtualDiskFlatVer2BackingInfo{
DiskMode: string(types.VirtualDiskModePersistent),
ThinProvisioned: types.NewBool(config.DiskThinProvisioned),
},
},
CapacityInKB: config.DiskSize * 1024,
}
devices.AssignController(disk, controller)
devices = append(devices, disk)
return devices, nil
}
func addNetwork(d *Driver, devices object.VirtualDeviceList, config *CreateConfig) (object.VirtualDeviceList, error) {
var network object.NetworkReference
if config.Network == "" {
h, err := d.FindHost(config.Host)
if err != nil {
return nil, err
}
i, err := h.Info("network")
if err != nil {
return nil, err
}
if len(i.Network) > 1 {
return nil, fmt.Errorf("Host has multiple networks. Specify it explicitly")
}
network = object.NewNetwork(d.client.Client, i.Network[0])
} else {
var err error
network, err = d.finder.Network(d.ctx, config.Network)
if err != nil {
return nil, err
}
}
backing, err := network.EthernetCardBackingInfo(d.ctx)
if err != nil {
return nil, err
}
device, err := object.EthernetCardTypes().CreateEthernetCard(config.NetworkCard, backing)
if err != nil {
return nil, err
}
return append(devices, device), nil
}
func (vm *VirtualMachine) AddCdrom(controllerType string, isoPath string) error {
devices, err := vm.vm.Device(vm.driver.ctx)
if err != nil {
return err
}
var controller *types.VirtualController
if controllerType == "sata" {
c, err := vm.FindSATAController()
if err != nil {
return err
}
controller = c.GetVirtualController()
} else {
c, err := devices.FindIDEController("")
if err != nil {
return err
}
controller = c.GetVirtualController()
}
cdrom, err := vm.CreateCdrom(controller)
if err != nil {
return err
}
if isoPath != "" {
devices.InsertIso(cdrom, isoPath)
}
log.Printf("Creating CD-ROM on controller '%v' with iso '%v'", controller, isoPath)
return vm.addDevice(cdrom)
}
func (vm *VirtualMachine) AddFloppy(imgPath string) error {
devices, err := vm.vm.Device(vm.driver.ctx)
if err != nil {
return err
}
floppy, err := devices.CreateFloppy()
if err != nil {
return err
}
if imgPath != "" {
floppy = devices.InsertImg(floppy, imgPath)
}
return vm.addDevice(floppy)
}
func (vm *VirtualMachine) SetBootOrder(order []string) error {
devices, err := vm.vm.Device(vm.driver.ctx)
if err != nil {
return err
}
bootOptions := types.VirtualMachineBootOptions{
BootOrder: devices.BootOrder(order),
}
return vm.vm.SetBootOptions(vm.driver.ctx, &bootOptions)
}
func (vm *VirtualMachine) RemoveDevice(keepFiles bool, device ...types.BaseVirtualDevice) error {
return vm.vm.RemoveDevice(vm.driver.ctx, keepFiles, device...)
}
func (vm *VirtualMachine) addDevice(device types.BaseVirtualDevice) error {
newDevices := object.VirtualDeviceList{device}
confSpec := types.VirtualMachineConfigSpec{}
var err error
confSpec.DeviceChange, err = newDevices.ConfigSpec(types.VirtualDeviceConfigSpecOperationAdd)
if err != nil {
return err
}
task, err := vm.vm.Reconfigure(vm.driver.ctx, confSpec)
if err != nil {
return err
}
_, err = task.WaitForResult(vm.driver.ctx, nil)
return err
}
func (vm *VirtualMachine) AddConfigParams(params map[string]string) error {
var confSpec types.VirtualMachineConfigSpec
var ov []types.BaseOptionValue
for k, v := range params {
o := types.OptionValue{
Key: k,
Value: v,
}
ov = append(ov, &o)
}
confSpec.ExtraConfig = ov
task, err := vm.vm.Reconfigure(vm.driver.ctx, confSpec)
if err != nil {
return err
}
_, err = task.WaitForResult(vm.driver.ctx, nil)
return err
}
func findNetworkAdapter(l object.VirtualDeviceList) (types.BaseVirtualEthernetCard, error) {
c := l.SelectByType((*types.VirtualEthernetCard)(nil))
if len(c) == 0 {
return nil, errors.New("no network adapter device found")
}
return c[0].(types.BaseVirtualEthernetCard), nil
}

View File

@ -0,0 +1,71 @@
package driver
import (
"errors"
"github.com/vmware/govmomi/vim25/types"
)
var (
ErrNoSataController = errors.New("no available SATA controller")
)
func (vm *VirtualMachine) AddSATAController() error {
sata := &types.VirtualAHCIController{}
return vm.addDevice(sata)
}
func (vm *VirtualMachine) FindSATAController() (*types.VirtualAHCIController, error) {
l, err := vm.Devices()
if err != nil {
return nil, err
}
c := l.PickController((*types.VirtualAHCIController)(nil))
if c == nil {
return nil, ErrNoSataController
}
return c.(*types.VirtualAHCIController), nil
}
func (vm *VirtualMachine) CreateCdrom(c *types.VirtualController) (*types.VirtualCdrom, error) {
l, err := vm.Devices()
if err != nil {
return nil, err
}
device := &types.VirtualCdrom{}
l.AssignController(device, c)
device.Backing = &types.VirtualCdromAtapiBackingInfo{
VirtualDeviceDeviceBackingInfo: types.VirtualDeviceDeviceBackingInfo{},
}
device.Connectable = &types.VirtualDeviceConnectInfo{
AllowGuestControl: true,
Connected: true,
StartConnected: true,
}
return device, nil
}
func (vm *VirtualMachine) EjectCdroms() error {
devices, err := vm.Devices()
if err != nil {
return err
}
cdroms := devices.SelectByType((*types.VirtualCdrom)(nil))
for _, cd := range cdroms {
c := cd.(*types.VirtualCdrom)
c.Backing = &types.VirtualCdromRemotePassthroughBackingInfo{}
c.Connectable = &types.VirtualDeviceConnectInfo{}
err := vm.vm.EditDevice(vm.driver.ctx, c)
if err != nil {
return err
}
}
return nil
}

View File

@ -0,0 +1,311 @@
package driver
import (
"context"
"log"
"net"
"testing"
"time"
)
func TestVMAcc_clone(t *testing.T) {
testCases := []struct {
name string
config *CloneConfig
checkFunction func(*testing.T, *VirtualMachine, *CloneConfig)
}{
{"Default", &CloneConfig{}, cloneDefaultCheck},
{"LinkedClone", &CloneConfig{LinkedClone: true}, cloneLinkedCloneCheck},
{"Folder", &CloneConfig{LinkedClone: true, Folder: "folder1/folder2"}, cloneFolderCheck},
{"ResourcePool", &CloneConfig{LinkedClone: true, ResourcePool: "pool1/pool2"}, cloneResourcePoolCheck},
{"Configure", &CloneConfig{LinkedClone: true}, configureCheck},
{"Configure_RAMReserveAll", &CloneConfig{LinkedClone: true}, configureRAMReserveAllCheck},
{"StartAndStop", &CloneConfig{LinkedClone: true}, startAndStopCheck},
{"Template", &CloneConfig{LinkedClone: true}, templateCheck},
{"Snapshot", &CloneConfig{}, snapshotCheck},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
tc.config.Host = TestHostName
tc.config.Name = newVMName()
templateName := "alpine"
d := newTestDriver(t)
template, err := d.FindVM(templateName) // Don't destroy this VM!
if err != nil {
t.Fatalf("Cannot find template vm '%v': %v", templateName, err)
}
log.Printf("[DEBUG] Clonning VM")
vm, err := template.Clone(context.TODO(), tc.config)
if err != nil {
t.Fatalf("Cannot clone vm '%v': %v", templateName, err)
}
defer destroyVM(t, vm, tc.config.Name)
log.Printf("[DEBUG] Running check function")
tc.checkFunction(t, vm, tc.config)
})
}
}
func cloneDefaultCheck(t *testing.T, vm *VirtualMachine, config *CloneConfig) {
d := vm.driver
// Check that the clone can be found by its name
if _, err := d.FindVM(config.Name); err != nil {
t.Errorf("Cannot find created vm '%v': %v", config.Name, err)
}
vmInfo, err := vm.Info("name", "parent", "runtime.host", "resourcePool", "datastore", "layoutEx.disk")
if err != nil {
t.Fatalf("Cannot read VM properties: %v", err)
}
if vmInfo.Name != config.Name {
t.Errorf("Invalid VM name: expected '%v', got '%v'", config.Name, vmInfo.Name)
}
f := d.NewFolder(vmInfo.Parent)
folderPath, err := f.Path()
if err != nil {
t.Fatalf("Cannot read folder name: %v", err)
}
if folderPath != "" {
t.Errorf("Invalid folder: expected '/', got '%v'", folderPath)
}
h := d.NewHost(vmInfo.Runtime.Host)
hostInfo, err := h.Info("name")
if err != nil {
t.Fatal("Cannot read host properties: ", err)
}
if hostInfo.Name != TestHostName {
t.Errorf("Invalid host name: expected '%v', got '%v'", TestHostName, hostInfo.Name)
}
p := d.NewResourcePool(vmInfo.ResourcePool)
poolPath, err := p.Path()
if err != nil {
t.Fatalf("Cannot read resource pool name: %v", err)
}
if poolPath != "" {
t.Errorf("Invalid resource pool: expected '/', got '%v'", poolPath)
}
dsr := vmInfo.Datastore[0].Reference()
ds := d.NewDatastore(&dsr)
dsInfo, err := ds.Info("name")
if err != nil {
t.Fatal("Cannot read datastore properties: ", err)
}
if dsInfo.Name != "datastore1" {
t.Errorf("Invalid datastore name: expected '%v', got '%v'", "datastore1", dsInfo.Name)
}
if len(vmInfo.LayoutEx.Disk[0].Chain) != 1 {
t.Error("Not a full clone")
}
}
func configureCheck(t *testing.T, vm *VirtualMachine, _ *CloneConfig) {
log.Printf("[DEBUG] Configuring the vm")
hwConfig := &HardwareConfig{
CPUs: 2,
CPUReservation: 1000,
CPULimit: 1500,
RAM: 2048,
RAMReservation: 1024,
MemoryHotAddEnabled: true,
CpuHotAddEnabled: true,
}
err := vm.Configure(hwConfig)
if err != nil {
t.Fatalf("Failed to configure VM: %v", err)
}
log.Printf("[DEBUG] Running checks")
vmInfo, err := vm.Info("config")
if err != nil {
t.Fatalf("Cannot read VM properties: %v", err)
}
cpuSockets := vmInfo.Config.Hardware.NumCPU
if cpuSockets != hwConfig.CPUs {
t.Errorf("VM should have %v CPU sockets, got %v", hwConfig.CPUs, cpuSockets)
}
cpuReservation := *vmInfo.Config.CpuAllocation.Reservation
if cpuReservation != hwConfig.CPUReservation {
t.Errorf("VM should have CPU reservation for %v Mhz, got %v", hwConfig.CPUReservation, cpuReservation)
}
cpuLimit := *vmInfo.Config.CpuAllocation.Limit
if cpuLimit != hwConfig.CPULimit {
t.Errorf("VM should have CPU reservation for %v Mhz, got %v", hwConfig.CPULimit, cpuLimit)
}
ram := vmInfo.Config.Hardware.MemoryMB
if int64(ram) != hwConfig.RAM {
t.Errorf("VM should have %v MB of RAM, got %v", hwConfig.RAM, ram)
}
ramReservation := *vmInfo.Config.MemoryAllocation.Reservation
if ramReservation != hwConfig.RAMReservation {
t.Errorf("VM should have RAM reservation for %v MB, got %v", hwConfig.RAMReservation, ramReservation)
}
cpuHotAdd := vmInfo.Config.CpuHotAddEnabled
if *cpuHotAdd != hwConfig.CpuHotAddEnabled {
t.Errorf("VM should have CPU hot add set to %v, got %v", hwConfig.CpuHotAddEnabled, cpuHotAdd)
}
memoryHotAdd := vmInfo.Config.MemoryHotAddEnabled
if *memoryHotAdd != hwConfig.MemoryHotAddEnabled {
t.Errorf("VM should have Memroy hot add set to %v, got %v", hwConfig.MemoryHotAddEnabled, memoryHotAdd)
}
}
func configureRAMReserveAllCheck(t *testing.T, vm *VirtualMachine, _ *CloneConfig) {
log.Printf("[DEBUG] Configuring the vm")
err := vm.Configure(&HardwareConfig{RAMReserveAll: true})
if err != nil {
t.Fatalf("Failed to configure VM: %v", err)
}
log.Printf("[DEBUG] Running checks")
vmInfo, err := vm.Info("config")
if err != nil {
t.Fatalf("Cannot read VM properties: %v", err)
}
if *vmInfo.Config.MemoryReservationLockedToMax != true {
t.Errorf("VM should have all RAM reserved")
}
}
func cloneLinkedCloneCheck(t *testing.T, vm *VirtualMachine, _ *CloneConfig) {
vmInfo, err := vm.Info("layoutEx.disk")
if err != nil {
t.Fatalf("Cannot read VM properties: %v", err)
}
if len(vmInfo.LayoutEx.Disk[0].Chain) != 2 {
t.Error("Not a linked clone")
}
}
func cloneFolderCheck(t *testing.T, vm *VirtualMachine, config *CloneConfig) {
vmInfo, err := vm.Info("parent")
if err != nil {
t.Fatalf("Cannot read VM properties: %v", err)
}
f := vm.driver.NewFolder(vmInfo.Parent)
path, err := f.Path()
if err != nil {
t.Fatalf("Cannot read folder name: %v", err)
}
if path != config.Folder {
t.Errorf("Wrong folder. expected: %v, got: %v", config.Folder, path)
}
}
func cloneResourcePoolCheck(t *testing.T, vm *VirtualMachine, config *CloneConfig) {
vmInfo, err := vm.Info("resourcePool")
if err != nil {
t.Fatalf("Cannot read VM properties: %v", err)
}
p := vm.driver.NewResourcePool(vmInfo.ResourcePool)
path, err := p.Path()
if err != nil {
t.Fatalf("Cannot read resource pool name: %v", err)
}
if path != config.ResourcePool {
t.Errorf("Wrong folder. expected: %v, got: %v", config.ResourcePool, path)
}
}
func startAndStopCheck(t *testing.T, vm *VirtualMachine, config *CloneConfig) {
stopper := startVM(t, vm, config.Name)
defer stopper()
switch ip, err := vm.WaitForIP(context.TODO()); {
case err != nil:
t.Errorf("Cannot obtain IP address from created vm '%v': %v", config.Name, err)
case net.ParseIP(ip) == nil:
t.Errorf("'%v' is not a valid ip address", ip)
}
err := vm.StartShutdown()
if err != nil {
t.Fatalf("Failed to initiate guest shutdown: %v", err)
}
log.Printf("[DEBUG] Waiting max 1m0s for shutdown to complete")
err = vm.WaitForShutdown(context.TODO(), 1*time.Minute)
if err != nil {
t.Fatalf("Failed to wait for giest shutdown: %v", err)
}
}
func snapshotCheck(t *testing.T, vm *VirtualMachine, config *CloneConfig) {
stopper := startVM(t, vm, config.Name)
defer stopper()
err := vm.CreateSnapshot("test-snapshot")
if err != nil {
t.Fatalf("Failed to create snapshot: %v", err)
}
vmInfo, err := vm.Info("layoutEx.disk")
if err != nil {
t.Fatalf("Cannot read VM properties: %v", err)
}
layers := len(vmInfo.LayoutEx.Disk[0].Chain)
if layers != 2 {
t.Errorf("VM should have a single snapshot. expected 2 disk layers, got %v", layers)
}
}
func templateCheck(t *testing.T, vm *VirtualMachine, _ *CloneConfig) {
err := vm.ConvertToTemplate()
if err != nil {
t.Fatalf("Failed to convert to template: %v", err)
}
vmInfo, err := vm.Info("config.template")
if err != nil {
t.Errorf("Cannot read VM properties: %v", err)
} else if !vmInfo.Config.Template {
t.Error("Not a template")
}
}
func startVM(t *testing.T, vm *VirtualMachine, vmName string) (stopper func()) {
log.Printf("[DEBUG] Starting the vm")
if err := vm.PowerOn(); err != nil {
t.Fatalf("Cannot start vm '%v': %v", vmName, err)
}
return func() {
log.Printf("[DEBUG] Powering off the vm")
if err := vm.PowerOff(); err != nil {
t.Errorf("Cannot power off started vm '%v': %v", vmName, err)
}
}
}
func destroyVM(t *testing.T, vm *VirtualMachine, vmName string) {
log.Printf("[DEBUG] Deleting the VM")
if err := vm.Destroy(); err != nil {
t.Errorf("!!! ERROR DELETING VM '%v': %v!!!", vmName, err)
}
// Check that the clone is no longer exists
if _, err := vm.driver.FindVM(vmName); err == nil {
t.Errorf("!!! STILL CAN FIND VM '%v'. IT MIGHT NOT HAVE BEEN DELETED !!!", vmName)
}
}

View File

@ -0,0 +1,91 @@
package driver
import (
"log"
"testing"
)
func TestVMAcc_create(t *testing.T) {
testCases := []struct {
name string
config *CreateConfig
checkFunction func(*testing.T, *VirtualMachine, *CreateConfig)
}{
{"MinimalConfiguration", &CreateConfig{}, createDefaultCheck},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
tc.config.Host = TestHostName
tc.config.Name = newVMName()
d := newTestDriver(t)
log.Printf("[DEBUG] Creating VM")
vm, err := d.CreateVM(tc.config)
if err != nil {
t.Fatalf("Cannot create VM: %v", err)
}
defer destroyVM(t, vm, tc.config.Name)
log.Printf("[DEBUG] Running check function")
tc.checkFunction(t, vm, tc.config)
})
}
}
func createDefaultCheck(t *testing.T, vm *VirtualMachine, config *CreateConfig) {
d := vm.driver
// Check that the clone can be found by its name
if _, err := d.FindVM(config.Name); err != nil {
t.Errorf("Cannot find created vm '%v': %v", config.Name, err)
}
vmInfo, err := vm.Info("name", "parent", "runtime.host", "resourcePool", "datastore", "layoutEx.disk")
if err != nil {
t.Fatalf("Cannot read VM properties: %v", err)
}
if vmInfo.Name != config.Name {
t.Errorf("Invalid VM name: expected '%v', got '%v'", config.Name, vmInfo.Name)
}
f := d.NewFolder(vmInfo.Parent)
folderPath, err := f.Path()
if err != nil {
t.Fatalf("Cannot read folder name: %v", err)
}
if folderPath != "" {
t.Errorf("Invalid folder: expected '/', got '%v'", folderPath)
}
h := d.NewHost(vmInfo.Runtime.Host)
hostInfo, err := h.Info("name")
if err != nil {
t.Fatal("Cannot read host properties: ", err)
}
if hostInfo.Name != TestHostName {
t.Errorf("Invalid host name: expected '%v', got '%v'", TestHostName, hostInfo.Name)
}
p := d.NewResourcePool(vmInfo.ResourcePool)
poolPath, err := p.Path()
if err != nil {
t.Fatalf("Cannot read resource pool name: %v", err)
}
if poolPath != "" {
t.Errorf("Invalid resource pool: expected '/', got '%v'", poolPath)
}
dsr := vmInfo.Datastore[0].Reference()
ds := d.NewDatastore(&dsr)
dsInfo, err := ds.Info("name")
if err != nil {
t.Fatal("Cannot read datastore properties: ", err)
}
if dsInfo.Name != "datastore1" {
t.Errorf("Invalid datastore name: expected 'datastore1', got '%v'", dsInfo.Name)
}
}

View File

@ -0,0 +1,82 @@
package driver
import (
"github.com/vmware/govmomi/vim25/methods"
"github.com/vmware/govmomi/vim25/types"
"golang.org/x/mobile/event/key"
"strings"
"unicode"
)
type KeyInput struct {
Message string
Scancode key.Code
Alt bool
Ctrl bool
Shift bool
}
var scancodeMap = make(map[rune]key.Code)
func init() {
scancodeIndex := make(map[string]key.Code)
scancodeIndex["abcdefghijklmnopqrstuvwxyz"] = key.CodeA
scancodeIndex["ABCDEFGHIJKLMNOPQRSTUVWXYZ"] = key.CodeA
scancodeIndex["1234567890"] = key.Code1
scancodeIndex["!@#$%^&*()"] = key.Code1
scancodeIndex[" "] = key.CodeSpacebar
scancodeIndex["-=[]\\"] = key.CodeHyphenMinus
scancodeIndex["_+{}|"] = key.CodeHyphenMinus
scancodeIndex[";'`,./"] = key.CodeSemicolon
scancodeIndex[":\"~<>?"] = key.CodeSemicolon
for chars, start := range scancodeIndex {
for i, r := range chars {
scancodeMap[r] = start + key.Code(i)
}
}
}
const shiftedChars = "!@#$%^&*()_+{}|:\"~<>?"
func (vm *VirtualMachine) TypeOnKeyboard(input KeyInput) (int32, error) {
var spec types.UsbScanCodeSpec
for _, r := range input.Message {
scancode := scancodeMap[r]
shift := input.Shift || unicode.IsUpper(r) || strings.ContainsRune(shiftedChars, r)
spec.KeyEvents = append(spec.KeyEvents, types.UsbScanCodeSpecKeyEvent{
// https://github.com/lamw/vghetto-scripts/blob/f74bc8ba20064f46592bcce5a873b161a7fa3d72/powershell/VMKeystrokes.ps1#L130
UsbHidCode: int32(scancode)<<16 | 7,
Modifiers: &types.UsbScanCodeSpecModifierType{
LeftControl: &input.Ctrl,
LeftAlt: &input.Alt,
LeftShift: &shift,
},
})
}
if input.Scancode != 0 {
spec.KeyEvents = append(spec.KeyEvents, types.UsbScanCodeSpecKeyEvent{
UsbHidCode: int32(input.Scancode)<<16 | 7,
Modifiers: &types.UsbScanCodeSpecModifierType{
LeftControl: &input.Ctrl,
LeftAlt: &input.Alt,
LeftShift: &input.Shift,
},
})
}
req := &types.PutUsbScanCodes{
This: vm.vm.Reference(),
Spec: spec,
}
resp, err := methods.PutUsbScanCodes(vm.driver.ctx, vm.driver.client.RoundTripper, req)
if err != nil {
return 0, err
}
return resp.Returnval, nil
}

View File

@ -0,0 +1,62 @@
{
"builders": [
{
"type": "vsphere-iso",
"vcenter_server": "vcenter.vsphere65.test",
"username": "root",
"password": "jetbrains",
"insecure_connection": true,
"vm_name": "alpine-{{timestamp}}",
"host": "esxi-1.vsphere65.test",
"CPUs": 1,
"RAM": 512,
"RAM_reserve_all": true,
"disk_controller_type": "pvscsi",
"disk_size": 1024,
"disk_thin_provisioned": true,
"network_card": "vmxnet3",
"guest_os_type": "other3xLinux64Guest",
"iso_paths": [
"[datastore1] ISO/alpine-standard-3.8.2-x86_64.iso"
],
"floppy_files": [
"{{template_dir}}/answerfile",
"{{template_dir}}/setup.sh"
],
"boot_wait": "15s",
"boot_command": [
"root<enter><wait>",
"mount -t vfat /dev/fd0 /media/floppy<enter><wait>",
"setup-alpine -f /media/floppy/answerfile<enter>",
"<wait5>",
"jetbrains<enter>",
"jetbrains<enter>",
"<wait5>",
"y<enter>",
"<wait10><wait10><wait10><wait10>",
"reboot<enter>",
"<wait10><wait10>",
"root<enter>",
"jetbrains<enter><wait>",
"mount -t vfat /dev/fd0 /media/floppy<enter><wait>",
"/media/floppy/SETUP.SH<enter>"
],
"ssh_username": "root",
"ssh_password": "jetbrains"
}
],
"provisioners": [
{
"type": "shell",
"inline": ["ls /"]
}
]
}

View File

@ -0,0 +1,15 @@
KEYMAPOPTS="us us"
HOSTNAMEOPTS="-n alpine"
INTERFACESOPTS="auto lo
iface lo inet loopback
auto eth0
iface eth0 inet dhcp
hostname alpine
"
TIMEZONEOPTS="-z UTC"
PROXYOPTS="none"
APKREPOSOPTS="http://mirror.yandex.ru/mirrors/alpine/v3.8/main"
SSHDOPTS="-c openssh"
NTPOPTS="-c none"
DISKOPTS="-m sys /dev/sda"

View File

@ -0,0 +1,19 @@
#!/bin/sh
set -ex
apk add libressl
apk add open-vm-tools
rc-update add open-vm-tools
/etc/init.d/open-vm-tools start
cat >/usr/local/bin/shutdown <<EOF
#!/bin/sh
poweroff
EOF
chmod +x /usr/local/bin/shutdown
sed -i "/#PermitRootLogin/c\PermitRootLogin yes" /etc/ssh/sshd_config
mkdir ~/.ssh
wget https://raw.githubusercontent.com/jetbrains-infra/packer-builder-vsphere/master/test/test-key.pub -O ~/.ssh/authorized_keys
/etc/init.d/sshd restart

View File

@ -0,0 +1,18 @@
{
"builders": [
{
"type": "vsphere-clone",
"vcenter_server": "vcenter.vsphere65.test",
"username": "root",
"password": "jetbrains",
"insecure_connection": "true",
"template": "alpine",
"vm_name": "alpine-clone-{{timestamp}}",
"host": "esxi-1.vsphere65.test",
"communicator": "none"
}
]
}

View File

@ -0,0 +1,25 @@
package main
import (
"fmt"
"github.com/jetbrains-infra/packer-builder-vsphere/driver"
)
func main() {
d, err := driver.NewDriver(&driver.ConnectConfig{
VCenterServer: "vcenter.vsphere65.test",
Username: "root",
Password: "jetbrains",
InsecureConnection: true,
})
if err != nil {
panic(err)
}
ds, err := d.FindDatastore("", "esxi-1.vsphere65.test")
if err != nil {
panic(err)
}
fmt.Println(ds.Name())
}

View File

@ -0,0 +1,51 @@
{
"builders": [
{
"type": "vsphere-iso",
"vcenter_server": "vcenter.vsphere65.test",
"username": "root",
"password": "jetbrains",
"insecure_connection": "true",
"vm_name": "macos-packer",
"host": "esxi-mac.vsphere65.test",
"guest_os_type": "darwin16_64Guest",
"CPUs": 1,
"RAM": 4096,
"disk_size": 32768,
"disk_thin_provisioned": true,
"network_card": "e1000e",
"usb_controller": true,
"configuration_parameters": {
"ich7m.present": "TRUE",
"smc.present": "TRUE"
},
"cdrom_type": "sata",
"iso_paths": [
"[datastore-mac] ISO/macOS 10.13.3.iso",
"[datastore-mac] ISO/VMware Tools/10.2.0/darwin.iso"
],
"iso_urls": ["{{template_dir}}/setup/out/setup.iso"],
"iso_checksum_type": "sha256",
"iso_checksum_url": "file:///{{template_dir}}/setup/out/sha256sums",
"boot_wait": "4m",
"boot_command": [
"<enter><wait5>",
"<leftCtrlOn><f2><leftCtrlOff>u<enter>t<enter><wait5>",
"/Volumes/setup/setup.sh<enter>"
],
"ssh_username": "jetbrains",
"ssh_password": "jetbrains"
}
]
}

View File

@ -0,0 +1 @@
out/

View File

@ -0,0 +1,15 @@
#!/bin/sh
set -eux
# Based on
# https://gist.github.com/agentsim/00cc38c693e7d0e1b36a2080870d955b#gistcomment-2304505
mkdir -p out
hdiutil create -o out/HighSierra.cdr -size 5530m -layout SPUD -fs HFS+J
hdiutil attach out/HighSierra.cdr.dmg -noverify -mountpoint /Volumes/install_build
sudo /Applications/Install\ macOS\ High\ Sierra.app/Contents/Resources/createinstallmedia --volume /Volumes/install_build --nointeraction
hdiutil detach /Volumes/Install\ macOS\ High\ Sierra
hdiutil convert out/HighSierra.cdr.dmg -format UDTO -o out/HighSierra.iso
mv out/HighSierra.iso.cdr out/HighSierra.iso
rm out/HighSierra.cdr.dmg

View File

@ -0,0 +1,27 @@
#!/bin/sh
set -eux
mkdir -p out/pkgroot
rm -rf /out/pkgroot/*
mkdir -p out/scripts
rm -rf /out/scripts/*
cp postinstall out/scripts/
pkgbuild \
--identifier io.packer.install \
--root out/pkgroot \
--scripts out/scripts \
out/postinstall.pkg
mkdir -p out/iso
rm -rf out/iso/*
cp setup.sh out/iso/
chmod +x out/iso/setup.sh
productbuild --package out/postinstall.pkg out/iso/postinstall.pkg
rm -f out/setup.iso
hdiutil makehybrid -iso -joliet -default-volume-name setup -o out/setup.iso out/iso
cd out
shasum -a 256 setup.iso >sha256sums

View File

@ -0,0 +1,25 @@
#!/bin/sh
set -eux
# debug output in /var/log/install.log
# Create user account
USERNAME=jetbrains
PASSWORD=jetbrains
dscl . -create "/Users/${USERNAME}"
dscl . -create "/Users/${USERNAME}" UserShell /bin/bash
dscl . -create "/Users/${USERNAME}" RealName "${USERNAME}"
dscl . -create "/Users/${USERNAME}" UniqueID 510
dscl . -create "/Users/${USERNAME}" PrimaryGroupID 20
dscl . -create "/Users/${USERNAME}" NFSHomeDirectory "/Users/${USERNAME}"
dscl . -passwd "/Users/${USERNAME}" "${PASSWORD}"
dscl . -append /Groups/admin GroupMembership "${USERNAME}"
createhomedir -c
# Start VMware Tools daemon explicitly
launchctl load /Library/LaunchDaemons/com.vmware.launchd.tools.plist
# Enable SSH
systemsetup -setremotelogin on
# Disable the welcome screen
touch "$3/private/var/db/.AppleSetupDone"

View File

@ -0,0 +1,13 @@
#!/bin/sh
set -eux
# Format partition
diskutil eraseDisk JHFS+ Disk disk0
# Packages are installed in reversed order - why?
"/Volumes/Image Volume/Install macOS High Sierra.app/Contents/Resources/startosinstall" \
--volume /Volumes/Disk \
--converttoapfs no \
--agreetolicense \
--installpackage "/Volumes/setup/postinstall.pkg" \
--installpackage "/Volumes/VMware Tools/Install VMware Tools.app/Contents/Resources/VMware Tools.pkg"

View File

@ -0,0 +1,16 @@
d-i passwd/user-fullname string jetbrains
d-i passwd/username string jetbrains
d-i passwd/user-password password jetbrains
d-i passwd/user-password-again password jetbrains
d-i user-setup/allow-password-weak boolean true
d-i partman-auto/disk string /dev/sda
d-i partman-auto/method string regular
d-i partman-partitioning/confirm_write_new_label boolean true
d-i partman/choose_partition select finish
d-i partman/confirm boolean true
d-i partman/confirm_nooverwrite boolean true
d-i pkgsel/include string open-vm-tools openssh-server
d-i finish-install/reboot_in_progress note

View File

@ -0,0 +1,62 @@
{
"builders": [
{
"type": "vsphere-iso",
"vcenter_server": "vcenter.vsphere65.test",
"username": "root",
"password": "jetbrains",
"insecure_connection": "true",
"vm_name": "example-ubuntu",
"host": "esxi-1.vsphere65.test",
"guest_os_type": "ubuntu64Guest",
"ssh_username": "jetbrains",
"ssh_password": "jetbrains",
"CPUs": 1,
"RAM": 1024,
"RAM_reserve_all": true,
"disk_controller_type": "pvscsi",
"disk_size": 32768,
"disk_thin_provisioned": true,
"network_card": "vmxnet3",
"iso_paths": [
"[datastore1] ISO/ubuntu-16.04.3-server-amd64.iso"
],
"floppy_files": [
"{{template_dir}}/preseed.cfg"
],
"boot_command": [
"<enter><wait><f6><wait><esc><wait>",
"<bs><bs><bs><bs><bs><bs><bs><bs><bs><bs>",
"<bs><bs><bs><bs><bs><bs><bs><bs><bs><bs>",
"<bs><bs><bs><bs><bs><bs><bs><bs><bs><bs>",
"<bs><bs><bs><bs><bs><bs><bs><bs><bs><bs>",
"<bs><bs><bs><bs><bs><bs><bs><bs><bs><bs>",
"<bs><bs><bs><bs><bs><bs><bs><bs><bs><bs>",
"<bs><bs><bs><bs><bs><bs><bs><bs><bs><bs>",
"<bs><bs><bs><bs><bs><bs><bs><bs><bs><bs>",
"<bs><bs><bs>",
"/install/vmlinuz",
" initrd=/install/initrd.gz",
" priority=critical",
" locale=en_US",
" file=/media/preseed.cfg",
"<enter>"
]
}
],
"provisioners": [
{
"type": "shell",
"inline": ["ls /"]
}
]
}

View File

@ -0,0 +1,2 @@
*.cmd text eol=crlf
*.ps1 text eol=crlf

View File

@ -0,0 +1,122 @@
<?xml version="1.0" encoding="utf-8"?>
<unattend xmlns="urn:schemas-microsoft-com:unattend">
<settings pass="windowsPE">
<component name="Microsoft-Windows-International-Core-WinPE" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<UILanguage>en-US</UILanguage>
</component>
<component name="Microsoft-Windows-PnpCustomizationsWinPE" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<DriverPaths>
<PathAndCredentials wcm:action="add" wcm:keyValue="A">
<!-- pvscsi-Windows8.flp -->
<Path>B:\</Path>
</PathAndCredentials>
</DriverPaths>
</component>
<component name="Microsoft-Windows-Setup" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<UserData>
<AcceptEula>true</AcceptEula>
<!-- Retail image requires a key
<ProductKey>
<Key>XXXXX-XXXXX-XXXXX-XXXXX-XXXXX</Key>
</ProductKey>
-->
</UserData>
<ImageInstall>
<OSImage>
<InstallFrom>
<MetaData wcm:action="add">
<Key>/IMAGE/NAME</Key>
<Value>Windows 10 Pro</Value>
</MetaData>
</InstallFrom>
<InstallToAvailablePartition>true</InstallToAvailablePartition>
</OSImage>
</ImageInstall>
<DiskConfiguration>
<Disk wcm:action="add">
<DiskID>0</DiskID>
<CreatePartitions>
<CreatePartition wcm:action="add">
<Order>1</Order>
<Extend>true</Extend>
<Type>Primary</Type>
</CreatePartition>
</CreatePartitions>
</Disk>
</DiskConfiguration>
</component>
</settings>
<settings pass="offlineServicing">
<component name="Microsoft-Windows-LUA-Settings" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<!-- Disable user account control -->
<EnableLUA>false</EnableLUA>
</component>
</settings>
<settings pass="specialize">
<component name="Microsoft-Windows-Deployment" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<RunSynchronous>
<RunSynchronousCommand wcm:action="add">
<Order>1</Order>
<!-- Install VMware Tools from windows.iso -->
<Path>a:\vmtools.cmd</Path>
<WillReboot>Always</WillReboot>
</RunSynchronousCommand>
</RunSynchronous>
</component>
</settings>
<settings pass="oobeSystem">
<component name="Microsoft-Windows-International-Core" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<SystemLocale>en-US</SystemLocale>
</component>
<component name="Microsoft-Windows-Shell-Setup" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<OOBE>
<!-- Privacy settings -->
<ProtectYourPC>3</ProtectYourPC>
</OOBE>
<!--
<TimeZone>Russian Standard Time</TimeZone>
-->
<UserAccounts>
<LocalAccounts>
<LocalAccount wcm:action="add">
<Name>jetbrains</Name>
<Password>
<Value>jetbrains</Value>
<PlainText>true</PlainText>
</Password>
<Group>Administrators</Group>
</LocalAccount>
</LocalAccounts>
</UserAccounts>
<AutoLogon>
<Enabled>true</Enabled>
<Username>jetbrains</Username>
<Password>
<Value>jetbrains</Value>
<PlainText>true</PlainText>
</Password>
<LogonCount>1</LogonCount>
</AutoLogon>
<FirstLogonCommands>
<SynchronousCommand wcm:action="add">
<Order>1</Order>
<!-- Enable WinRM service -->
<CommandLine>powershell -ExecutionPolicy Bypass -File a:\setup.ps1</CommandLine>
<RequiresUserInput>true</RequiresUserInput>
</SynchronousCommand>
</FirstLogonCommands>
</component>
</settings>
</unattend>

View File

@ -0,0 +1,15 @@
$ErrorActionPreference = "Stop"
# Switch network connection to private mode
# Required for WinRM firewall rules
$profile = Get-NetConnectionProfile
Set-NetConnectionProfile -Name $profile.Name -NetworkCategory Private
# Enable WinRM service
winrm quickconfig -quiet
winrm set winrm/config/service '@{AllowUnencrypted="true"}'
winrm set winrm/config/service/auth '@{Basic="true"}'
# Reset auto logon count
# https://docs.microsoft.com/en-us/windows-hardware/customize/desktop/unattend/microsoft-windows-shell-setup-autologon-logoncount#logoncount-known-issue
Set-ItemProperty -Path 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon' -Name AutoLogonCount -Value 0

View File

@ -0,0 +1,2 @@
@rem Silent mode, basic UI, no reboot
e:\setup64 /s /v "/qb REBOOT=R"

View File

@ -0,0 +1,48 @@
{
"builders": [
{
"type": "vsphere-iso",
"vcenter_server": "vcenter.vsphere65.test",
"username": "root",
"password": "jetbrains",
"insecure_connection": "true",
"vm_name": "example-windows",
"host": "esxi-1.vsphere65.test",
"guest_os_type": "windows9_64Guest",
"communicator": "winrm",
"winrm_username": "jetbrains",
"winrm_password": "jetbrains",
"CPUs": 1,
"RAM": 4096,
"RAM_reserve_all": true,
"disk_controller_type": "pvscsi",
"disk_size": 32768,
"disk_thin_provisioned": true,
"network_card": "vmxnet3",
"iso_paths": [
"[datastore1] ISO/en_windows_10_multi-edition_vl_version_1709_updated_dec_2017_x64_dvd_100406172.iso",
"[datastore1] ISO/VMware Tools/10.2.0/windows.iso"
],
"floppy_files": [
"{{template_dir}}/setup/"
],
"floppy_img_path": "[datastore1] ISO/VMware Tools/10.2.0/pvscsi-Windows8.flp"
}
],
"provisioners": [
{
"type": "windows-shell",
"inline": ["dir c:\\"]
}
]
}

11
builder/vsphere/go.mod Normal file
View File

@ -0,0 +1,11 @@
module github.com/jetbrains-infra/packer-builder-vsphere
require (
github.com/hashicorp/packer v1.4.2
github.com/vmware/govmomi v0.20.0
go.uber.org/goleak v0.10.1-0.20190517053103-3b0196519f16
golang.org/x/mobile v0.0.0-20190607214518-6fa95d984e88
)
replace git.apache.org/thrift.git => github.com/apache/thrift v0.0.0-20180902110319-2566ecd5d999

468
builder/vsphere/go.sum Normal file
View File

@ -0,0 +1,468 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.31.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.36.0 h1:+aCSj7tOo2LODWVEuZDZeGCckdt6MlSF+X/rB3wUiS8=
cloud.google.com/go v0.36.0/go.mod h1:RUoy9p/M4ge0HzT8L+SDZ8jg+Q6fth0CiBuhFJpSV40=
contrib.go.opencensus.io/exporter/ocagent v0.5.0/go.mod h1:ImxhfLRpxoYiSq891pBrLVhN+qmP8BTVvdH2YLs7Gl0=
dmitri.shuralyov.com/app/changes v0.0.0-20180602232624-0a106ad413e3/go.mod h1:Yl+fi1br7+Rr3LqpNJf1/uxUdtRUV+Tnj0o93V2B9MU=
dmitri.shuralyov.com/html/belt v0.0.0-20180602232347-f7d459c86be0/go.mod h1:JLBrvjyP0v+ecvNYvCpyZgu5/xkfAUhi6wJj28eUfSU=
dmitri.shuralyov.com/service/change v0.0.0-20181023043359-a85b471d5412/go.mod h1:a1inKt/atXimZ4Mv927x+r7UpyzRUf4emIoiiSC2TN4=
dmitri.shuralyov.com/state v0.0.0-20180228185332-28bcc343414c/go.mod h1:0PRwlb0D6DFvNNtx+9ybjezNCa8XF0xaYcETyp6rHWU=
git.apache.org/thrift.git v0.0.0-20180902110319-2566ecd5d999/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg=
github.com/1and1/oneandone-cloudserver-sdk-go v1.0.1/go.mod h1:61apmbkVJH4kg+38ftT+/l0XxdUCVnHggqcOTqZRSEE=
github.com/Azure/azure-sdk-for-go v30.0.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc=
github.com/Azure/go-autorest v12.0.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24=
github.com/Azure/go-ntlmssp v0.0.0-20180810175552-4a21cbd618b4 h1:pSm8mp0T2OH2CPmPDPtwHPr3VAQaOwVF/JbllOPP4xA=
github.com/Azure/go-ntlmssp v0.0.0-20180810175552-4a21cbd618b4/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/ChrisTrenkamp/goxpath v0.0.0-20170625215350-4fe035839290 h1:K9I21XUHNbYD3GNMmJBN0UKJCpdP+glftwNZ7Bo8kqY=
github.com/ChrisTrenkamp/goxpath v0.0.0-20170625215350-4fe035839290/go.mod h1:nuWgzSkT5PnyOd+272uUmV0dnAnAn42Mk7PiQC5VzN4=
github.com/NaverCloudPlatform/ncloud-sdk-go v0.0.0-20180110055012-c2e73f942591/go.mod h1:EHGzQGbwozJBj/4qj3WGrTJ0FqjgOTOxLQ0VNWvPn08=
github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo=
github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI=
github.com/Telmate/proxmox-api-go v0.0.0-20190614181158-26cd147831a4/go.mod h1:OGWyIMJ87/k/GCz8CGiWB2HOXsOVDM6Lpe/nFPkC4IQ=
github.com/abdullin/seq v0.0.0-20160510034733-d5467c17e7af/go.mod h1:5Jv4cbFiHJMsVxt52+i0Ha45fjshj6wxYr1r19tB9bw=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/aliyun/alibaba-cloud-sdk-go v0.0.0-20190418113227-25233c783f4e/go.mod h1:T9M45xf79ahXVelWoOBmH0y4aC1t5kXO5BxwyakgIGA=
github.com/aliyun/aliyun-oss-go-sdk v0.0.0-20170113022742-e6dbea820a9f/go.mod h1:T/Aws4fEfogEE9v+HPhhw+CntffsBHJ8nXQCwKr0/g8=
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c=
github.com/antchfx/xpath v0.0.0-20170728053731-b5c552e1acbd h1:S3Fr6QnkpW9VRjiEY4psQHhhbbahASuNVj52YIce7lI=
github.com/antchfx/xpath v0.0.0-20170728053731-b5c552e1acbd/go.mod h1:Yee4kTMuNiPYJ7nSNorELQMr1J33uOpXDMByNYhvtNk=
github.com/antchfx/xquery v0.0.0-20170730121040-eb8c3c172607 h1:BFFG6KP8ASFBg2ptWsJn8p8RDufBjBDKIxLU7BTYGOM=
github.com/antchfx/xquery v0.0.0-20170730121040-eb8c3c172607/go.mod h1:LzD22aAzDP8/dyiCKFp31He4m2GPjl0AFyzDtZzUu9M=
github.com/antihax/optional v0.0.0-20180407024304-ca021399b1a6/go.mod h1:V8iCPQYkqmusNa815XgQio277wI47sdRh1dUOLdyC6Q=
github.com/apache/thrift v0.0.0-20180902110319-2566ecd5d999/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
github.com/approvals/go-approval-tests v0.0.0-20160714161514-ad96e53bea43/go.mod h1:S6puKjZ9ZeqUPBv2hEBnMZGcM2J6mOsDRQcmxkMAND0=
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 h1:8GUt8eRujhVEGZFFEjBj46YV4rDjvGrNxb0KMWYkL2I=
github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
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.15.78/go.mod h1:E3/ieXAlvM0XWO57iftYVDLLvQ824smPP3ATZkfNZeM=
github.com/aws/aws-sdk-go v1.16.24 h1:I/A3Hwbgs3IEAP6v1bFpHKXiT7wZDoToX9cb00nxZnM=
github.com/aws/aws-sdk-go v1.16.24/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
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/biogo/hts v0.0.0-20160420073057-50da7d4131a3/go.mod h1:YOY5xnRf7Jz2SZCLSKgVfyqNzbRgyTznM3HyDqQMxcU=
github.com/bradfitz/go-smtpd v0.0.0-20170404230938-deb6d6237625/go.mod h1:HYsPBTaaSFSlLx/70C2HPIMNZpVV8+vt/A+FMnYP11g=
github.com/c2h5oh/datasize v0.0.0-20171227191756-4eba002a5eae/go.mod h1:S/7n9copUssQ56c7aAgHqftWO4LTf4xY6CGWt8Bc+3M=
github.com/census-instrumentation/opencensus-proto v0.2.0/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/cheggaaa/pb v1.0.27 h1:wIkZHkNfC7R6GI5w7l/PdAdzXzlrbcI3p8OAlnkTsnc=
github.com/cheggaaa/pb v1.0.27/go.mod h1:pQciLPpbU0oxA0h+VJYYLxO+XeDQb5pZijXscXHm81s=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/coreos/go-systemd v0.0.0-20181012123002-c6f51f82210d/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/creack/goselect v0.1.0/go.mod h1:gHrIcH/9UZDn2qgeTUeW5K9eZsVYCH6/60J/FHysWyE=
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/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
github.com/digitalocean/godo v1.11.1/go.mod h1:h6faOIcZ8lWIwNQ+DN7b3CgX4Kwby5T+nbpNqkUIozU=
github.com/dnaeon/go-vcr v1.0.0/go.mod h1:aBB1+wY4s93YsC3HHjMBMrwTj2R9FHDzUr9KyGc8n1E=
github.com/docker/docker v0.0.0-20180422163414-57142e89befe/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
github.com/dustin/go-humanize v1.0.0/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/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs=
github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU=
github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I=
github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc=
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/gliderlabs/ssh v0.1.1/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0=
github.com/go-ini/ini v1.25.4/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8=
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/gofrs/flock v0.7.1 h1:DP+LD/t0njgoPBvT5MJLeliUIVQR03hiKR6vezdwHlc=
github.com/gofrs/flock v0.7.1/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E=
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/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db h1:woRePGFeVFfLKN/pOkfl+p/TAqKOfFu+7KPlMVpok/w=
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c h1:964Od4U6p2jUkFxvCydnIczKteheJEzHRToSGK3Bnlw=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/go-cmp v0.2.0 h1:+dTQ8DZQJz0Mb/HjFlkptS1FeQ4cWSnN941F8aEG4SQ=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ=
github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
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/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/shlex v0.0.0-20150127133951-6f45313302b9/go.mod h1:RpwtwJQFrIEPstU94h88MWPXP2ektJZ8cZ0YntAmXiE=
github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/googleapis/gax-go v2.0.0+incompatible h1:j0GKcs05QVmm7yesiZq2+9cxHkNK9YM6zKx4D2qucQU=
github.com/googleapis/gax-go v2.0.0+incompatible/go.mod h1:SFVmujtThgffbyetf+mdk2eWhX2bMyUtNHzFKcPA9HY=
github.com/googleapis/gax-go/v2 v2.0.3 h1:siORttZ36U2R/WjiJuDz8znElWBiAlO9rVt+mqJt0Cc=
github.com/googleapis/gax-go/v2 v2.0.3/go.mod h1:LLvjysVCY1JZeum8Z6l8qUty8fiNwE08qbEPm1M08qg=
github.com/gophercloud/gophercloud v0.0.0-20180903124057-ea7289ebdf06/go.mod h1:3WdhXV3rUYy9p6AUW8d94kr+HS62Y4VL9mBnFxsD8q4=
github.com/gophercloud/utils v0.0.0-20190124192022-a5c25e7a53a6/go.mod h1:wjDF8z83zTeg5eMLml5EBSlAhbF7G8DobyI1YsMuyzw=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
github.com/gorilla/websocket v0.0.0-20170319172727-a91eba7f9777/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=
github.com/grpc-ecosystem/grpc-gateway v1.5.0/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpgL2+G+NZTnrVHpWWfpdw=
github.com/grpc-ecosystem/grpc-gateway v1.8.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
github.com/hashicorp/consul v1.4.0 h1:PQTW4xCuAExEiSbhrsFsikzbW5gVBoi74BjUvYFyKHw=
github.com/hashicorp/consul v1.4.0/go.mod h1:mFrjN1mfidgJfYP1xrJCF+AfRhr6Eaqhb2+sfyn/OOI=
github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA=
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/go-checkpoint v0.0.0-20171009173528-1545e56e46de h1:XDCSythtg8aWSRSO29uwhgh7b127fWr+m5SemqjSUL8=
github.com/hashicorp/go-checkpoint v0.0.0-20171009173528-1545e56e46de/go.mod h1:xIwEieBHERyEvaeKF/TcHh1Hu+lxPM+n2vT1+g9I4m4=
github.com/hashicorp/go-cleanhttp v0.5.0 h1:wvCrVc9TjDls6+YGAF2hAifE1E5U1+b4tH6KdvN3Gig=
github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
github.com/hashicorp/go-getter v1.2.0 h1:E05bVPilzyh2yXgT6srn7WEkfMZaH+LuX9tDJw/4kaE=
github.com/hashicorp/go-getter v1.2.0/go.mod h1:/O1k/AizTN0QmfEKknCYGvICeyKUDqCYA8vvWtGWDeQ=
github.com/hashicorp/go-immutable-radix v1.0.0 h1:AKDB1HM5PWEA7i4nhcpwOrO2byshxBjXVn/J/3+z5/0=
github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
github.com/hashicorp/go-msgpack v0.5.3 h1:zKjpN5BK/P5lMYrLmBHdBULWbJ0XpYR+7NGzqkZzoD4=
github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
github.com/hashicorp/go-multierror v1.0.0 h1:iVjPR7a6H0tWELX5NxNe7bYopibicUzc7uPribsnS6o=
github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
github.com/hashicorp/go-oracle-terraform v0.0.0-20181016190316-007121241b79/go.mod h1:09jT3Y/OIsjTjQ2+3bkVNPDKqWcGIYYvjB2BEKVUdvc=
github.com/hashicorp/go-retryablehttp v0.5.2 h1:AoISa4P4IsW0/m4T6St8Yw38gTl5GtBAgfkhYh1xAz4=
github.com/hashicorp/go-retryablehttp v0.5.2/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs=
github.com/hashicorp/go-rootcerts v0.0.0-20160503143440-6bb64b370b90 h1:VBj0QYQ0u2MCJzBfeYXGexnAl17GsH1yidnoxCqqD9E=
github.com/hashicorp/go-rootcerts v0.0.0-20160503143440-6bb64b370b90/go.mod h1:o4zcYY1e0GEZI6eSEr+43QDYmuGglw1qSO6qdHUHCgg=
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 h1:GeH6tui99pF4NJgfnhp+L6+FfobzVW3Ah46sLo0ICXs=
github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU=
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 h1:fv1ep09latC32wFoVwnqcnKJGnMSdBanPczbHAYm1BE=
github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go-version v1.0.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/hashicorp/go-version v1.1.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/hashicorp/go-version v1.2.0 h1:3vNe/fWF5CBgRIguda1meWhsZHy3m8gCJ5wx+dIzX/E=
github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90=
github.com/hashicorp/golang-lru v0.5.0 h1:CL2msUPvZTLb5O648aiLNJw3hnBxN2+1Jq8rCOH9wdo=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
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/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=
github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ=
github.com/hashicorp/memberlist v0.1.3 h1:EmmoJme1matNzb+hMpDuR/0sbJSUisxyqBGG676r31M=
github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I=
github.com/hashicorp/packer v1.4.2 h1:4WE9HLrht/bNefy7GFR4eyLmd18caM5K4RCvVUxur40=
github.com/hashicorp/packer v1.4.2/go.mod h1:uesgTpUABDx76pwrYvB7sD2zBMxUvCYU04/hmz4pIi8=
github.com/hashicorp/serf v0.8.2 h1:YZ7UKsJv+hKjqGVUUbtE3HNj79Eln2oQ75tniF6iPt0=
github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=
github.com/hashicorp/vault v1.1.0 h1:v79NUgO5xCZnXVzUkIqFOXtP8YhpnHAi1fk3eo9cuOE=
github.com/hashicorp/vault v1.1.0/go.mod h1:KfSyffbKxoVyspOdlaGVjIuwLobi07qD1bAbosPMpP0=
github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d h1:kJCB4vdITiW1eC1vq2e6IsrXKrZit1bv/TDYFGMp4BQ=
github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM=
github.com/hetznercloud/hcloud-go v1.12.0/go.mod h1:g5pff0YNAZywQaivY/CmhUYFVp7oP0nu3MiODC2W4Hw=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/hyperonecom/h1-client-go v0.0.0-20190122232013-cf38e8387775/go.mod h1:R9rU87RxxmcD3DkspW9JqGBXiJyg5MA+WNCtJrBtnXs=
github.com/jellevandenhooff/dkim v0.0.0-20150330215556-f50fe3d243e1/go.mod h1:E0B/fFc00Y+Rasa88328GlI/XbtyysCtTHZS8h7IrBU=
github.com/jmespath/go-jmespath v0.0.0-20160202185014-0b12d6b521d8/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af h1:pmfjZENx5imkbgOkpRUYLnmbU7UEFbjtDA2hxJ1ichM=
github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
github.com/joyent/triton-go v0.0.0-20180116165742-545edbe0d564/go.mod h1:U+RSyWxWd04xTqnuOQxnai7XGS2PrPY2cfGoDKtMHjA=
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/jtolds/gls v4.2.1+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
github.com/kardianos/osext v0.0.0-20170510131534-ae77be60afb1/go.mod h1:1NbS8ALrpOvjt0rHPNLyCIeMtbizbir8U//inJ+zuB8=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/klauspost/compress v0.0.0-20160131094358-f86d2e6d8a77/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
github.com/klauspost/cpuid v0.0.0-20160106104451-349c67577817/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
github.com/klauspost/crc32 v0.0.0-20160114101742-999f3125931f/go.mod h1:+ZoRqAPRLkC4NPOvfYeR5KNOrY6TD+/sAC3HXPZgDYg=
github.com/klauspost/pgzip v0.0.0-20151221113845-47f36e165cec/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/fs v0.0.0-20131111012553-2788f0dbd169 h1:YUrU1/jxRqnt0PSrKj1Uj/wEjk/fjnE80QFfi2Zlj7Q=
github.com/kr/fs v0.0.0-20131111012553-2788f0dbd169/go.mod h1:glhvuHOU9Hy7/8PwwdtnarXqLagOX0b/TbZx2zLMqEg=
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/pty v1.1.3/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348/go.mod h1:B69LEHPfb2qLo0BaaOLcbitczOKLWTsrBG9LczfCD4k=
github.com/linode/linodego v0.7.1/go.mod h1:ga11n3ivecUrPCHN0rANxKmfWBJVkOXfLMZinAbj2sY=
github.com/marstr/guid v0.0.0-20170427235115-8bdf7d1a087c/go.mod h1:74gB1z2wpxxInTG6yaqA7KrtM0NZ+RbrcqDvYHefzho=
github.com/masterzen/azure-sdk-for-go v0.0.0-20161014135628-ee4f0065d00c h1:FMUOnVGy8nWk1cvlMCAoftRItQGMxI0vzJ3dQjeZTCE=
github.com/masterzen/azure-sdk-for-go v0.0.0-20161014135628-ee4f0065d00c/go.mod h1:mf8fjOu33zCqxUjuiU3I8S1lJMyEAlH+0F2+M5xl3hE=
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-20180224160350-7e40f93ae939 h1:cRFHA33ER97Xy5jmjS519OXCS/yE3AT3zdbQAg0Z53g=
github.com/masterzen/winrm v0.0.0-20180224160350-7e40f93ae939/go.mod h1:CfZSN7zwz5gJiFhZJz49Uzk7mEBHIceWmbFmYx7Hf7E=
github.com/mattn/go-colorable v0.0.9 h1:UVL0vNpWh04HeJXV0KLcaT7r06gOH2l4OW6ddYRUIY4=
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
github.com/mattn/go-isatty v0.0.4 h1:bnP0vzxcAdeI1zdubAl5PjU6zsERjGZb7raWodagDYs=
github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
github.com/mattn/go-runewidth v0.0.4 h1:2BvfKmzob6Bmd4YsL0zygOqfdFnK7GR4QL06Do4/p7Y=
github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
github.com/mattn/go-tty v0.0.0-20190424173100-523744f04859/go.mod h1:XPvLUNfbS4fJH25nqRHfWLMa1ONC8Amw+mIA639KxkE=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/microcosm-cc/bluemonday v1.0.1/go.mod h1:hsXNsILzKxV+sX77C5b8FSuKF00vh2OMYv+xgHpAMF4=
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
github.com/miekg/dns v1.1.1 h1:DVkblRdiScEnEr0LR9nTnEQqHYycjkXW9bOjd+2EL2o=
github.com/miekg/dns v1.1.1/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
github.com/mitchellh/go-fs v0.0.0-20180402234041-7b48fa161ea7 h1:PXPMDtfqV+rZJshQHOiwUFqlqErXaAcuWy+/ZmyRfNc=
github.com/mitchellh/go-fs v0.0.0-20180402234041-7b48fa161ea7/go.mod h1:g7SZj7ABpStq3tM4zqHiVEG5un/DZ1+qJJKO7qx1EvU=
github.com/mitchellh/go-homedir v1.0.0 h1:vKb8ShqSby24Yrqr/yDYkuFz8d0WUjys40rvnGC8aR0=
github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/go-testing-interface v1.0.0 h1:fzU/JVNcaqHQEcVFAKeR41fkiLdIPrefOvVG1VZ96U0=
github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
github.com/mitchellh/go-vnc v0.0.0-20150629162542-723ed9867aed/go.mod h1:3rdaFaCv4AyBgu5ALFM0+tSuHrBh6v692nyQe3ikrq0=
github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg=
github.com/mitchellh/gox v1.0.1/go.mod h1:ED6BioOGXMswlXa2zxfh/xdd5QhwYliBFn9V18Ap4z4=
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 v0.0.0-20180111000720-b4575eea38cc h1:5T6hzGUO5OrL6MdYXYoLQtRWJDDgjdlOVBn9mIqGY1g=
github.com/mitchellh/mapstructure v0.0.0-20180111000720-b4575eea38cc/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/mitchellh/panicwrap v0.0.0-20170106182340-fce601fe5557/go.mod h1:QuAqW7/z+iv6aWFJdrA8kCbsF0OOJVKCICqTcYBexuY=
github.com/mitchellh/prefixedio v0.0.0-20151214002211-6e6954073784/go.mod h1:kB1naBgV9ORnkiTVeyJOI1DavaJkG4oNIq0Af6ZVKUo=
github.com/mitchellh/reflectwalk v1.0.0 h1:9D+8oIskB4VJBN5SFlmc27fSlIBZaov1Wpk/IfikLNY=
github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
github.com/mna/pigeon v1.0.0/go.mod h1:Iym28+kJVnC1hfQvv5MUtI6AiFFzvQjHcvI4RFTG/04=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/moul/anonuuid v0.0.0-20160222162117-609b752a95ef/go.mod h1:LgKrp0Iss/BVwquptq4eIe6HPr0s3t1WHT5x0qOh14U=
github.com/moul/gotty-client v0.0.0-20180327180212-b26a57ebc215/go.mod h1:CxM/JGtpRrEPve5H04IhxJrGhxgwxMc6jSP2T4YD60w=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32/go.mod h1:9wM+0iRr9ahx58uYLpLIr5fm8diHn0JbqRycJi6w0Ms=
github.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86/go.mod h1:kHJEU3ofeGjhHklVoIGuVj85JJwZ6kWPaJwCIxgnFmo=
github.com/neelance/sourcemap v0.0.0-20151028013722-8c68805598ab/go.mod h1:Qr6/a/Q4r9LP1IltGz7tA7iOK1WonHEYhu1HRBA7ZiM=
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/olekukonko/tablewriter v0.0.0-20180105111133-96aac992fc8b/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
github.com/openzipkin/zipkin-go v0.1.1/go.mod h1:NtoC/o8u3JlF1lSlyPNswIbeQH9bJTmOf0Erfk+hxe8=
github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw=
github.com/oracle/oci-go-sdk v1.8.0/go.mod h1:VQb79nF8Z2cwLkLS35ukwStZIg5F66tcBccjip/j888=
github.com/packer-community/winrmcp v0.0.0-20180921204643-0fd363d6159a h1:A3QMuteviunoaY/8ex+RKFqwhcZJ/Cf3fCW3IwL2wx4=
github.com/packer-community/winrmcp v0.0.0-20180921204643-0fd363d6159a/go.mod h1:f6Izs6JvFTdnRbziASagjZ2vmf55NSIkC/weStxCHqk=
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c h1:Lgl0gzECD8GnQ5QCWA8o6BtfL6mDH5rQgM4/fX3avOs=
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
github.com/pierrec/lz4 v2.0.5+incompatible h1:2xWsjqPFWcplujydGg4WmhC/6fZqK42wMM8aXeqhl0I=
github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
github.com/pkg/errors v0.8.0 h1:WdK/asTD0HN+q6hsWO3/vpuAkAr+tw6aNJNDFFf0+qw=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/sftp v0.0.0-20160118190721-e84cc8c755ca h1:k8gsErq3rkcbAyCnpOycQsbw88NjCHk7L3KfBZKhQDQ=
github.com/pkg/sftp v0.0.0-20160118190721-e84cc8c755ca/go.mod h1:NxmoDg/QLVWluQDUYG7XBZTLUpKeFa8e3aMf1BfjyHk=
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/profitbricks/profitbricks-sdk-go v4.0.2+incompatible/go.mod h1:T3/WrziK7fYH3C8ilAFAHe99R452/IzIG3YYkqaOFeQ=
github.com/prometheus/client_golang v0.8.0/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs=
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
github.com/prometheus/procfs v0.0.0-20180725123919-05ee40e3a273/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-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
github.com/renstrom/fuzzysearch v0.0.0-20160331204855-2d205ac6ec17/go.mod h1:SAEjPB4voP88qmWJXI7mA5m15uNlEnuHLx4Eu2mPGpQ=
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
github.com/rwtodd/Go.Sed v0.0.0-20170507045331-d6d5d585814e h1:lN+IKs+Jb9uwDOMO4VJZzH9vOjjist0THR5s9akp+Ss=
github.com/rwtodd/Go.Sed v0.0.0-20170507045331-d6d5d585814e/go.mod h1:8AEUvGVi2uQ5b24BIhcr0GCcpd/RNAFWaN2CJFrWIIQ=
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
github.com/ryanuber/go-glob v0.0.0-20170128012129-256dc444b735 h1:7YvPJVmEeFHR1Tj9sZEYsmarJEQfMVYpd/Vyy/A8dqE=
github.com/ryanuber/go-glob v0.0.0-20170128012129-256dc444b735/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/scaleway/scaleway-cli v0.0.0-20180921094345-7b12c9699d70/go.mod h1:XjlXWPd6VONhsRSEuzGkV8mzRpH7ou1cdLV7IKJk96s=
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/shurcooL/component v0.0.0-20170202220835-f88ec8f54cc4/go.mod h1:XhFIlyj5a1fBNx5aJTbKoIq0mNaPvOagO+HjB3EtxrY=
github.com/shurcooL/events v0.0.0-20181021180414-410e4ca65f48/go.mod h1:5u70Mqkb5O5cxEA8nxTsgrgLehJeAw6Oc4Ab1c/P1HM=
github.com/shurcooL/github_flavored_markdown v0.0.0-20181002035957-2122de532470/go.mod h1:2dOwnU2uBioM+SGy2aZoq1f/Sd1l9OkAeAUvjSyvgU0=
github.com/shurcooL/go v0.0.0-20180423040247-9e1955d9fb6e/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk=
github.com/shurcooL/go-goon v0.0.0-20170922171312-37c2f522c041/go.mod h1:N5mDOmsrJOB+vfqUK+7DmDyjhSLIIBnXo9lvZJj3MWQ=
github.com/shurcooL/gofontwoff v0.0.0-20180329035133-29b52fc0a18d/go.mod h1:05UtEgK5zq39gLST6uB0cf3NEHjETfB4Fgr3Gx5R9Vw=
github.com/shurcooL/gopherjslib v0.0.0-20160914041154-feb6d3990c2c/go.mod h1:8d3azKNyqcHP1GaQE/c6dDgjkgSx2BZ4IoEi4F1reUI=
github.com/shurcooL/highlight_diff v0.0.0-20170515013008-09bb4053de1b/go.mod h1:ZpfEhSmds4ytuByIcDnOLkTHGUI6KNqRNPDLHDk+mUU=
github.com/shurcooL/highlight_go v0.0.0-20181028180052-98c3abbbae20/go.mod h1:UDKB5a1T23gOMUJrI+uSuH0VRDStOiUVSjBTRDVBVag=
github.com/shurcooL/home v0.0.0-20181020052607-80b7ffcb30f9/go.mod h1:+rgNQw2P9ARFAs37qieuu7ohDNQ3gds9msbT2yn85sg=
github.com/shurcooL/htmlg v0.0.0-20170918183704-d01228ac9e50/go.mod h1:zPn1wHpTIePGnXSHpsVPWEktKXHr6+SS6x/IKRb7cpw=
github.com/shurcooL/httperror v0.0.0-20170206035902-86b7830d14cc/go.mod h1:aYMfkZ6DWSJPJ6c4Wwz3QtW22G7mf/PEgaB9k/ik5+Y=
github.com/shurcooL/httpfs v0.0.0-20171119174359-809beceb2371/go.mod h1:ZY1cvUeJuFPAdZ/B6v7RHavJWZn2YPVFQ1OSXhCGOkg=
github.com/shurcooL/httpgzip v0.0.0-20180522190206-b1c53ac65af9/go.mod h1:919LwcH0M7/W4fcZ0/jy0qGght1GIhqyS/EgWGH2j5Q=
github.com/shurcooL/issues v0.0.0-20181008053335-6292fdc1e191/go.mod h1:e2qWDig5bLteJ4fwvDAc2NHzqFEthkqn7aOZAOpj+PQ=
github.com/shurcooL/issuesapp v0.0.0-20180602232740-048589ce2241/go.mod h1:NPpHK2TI7iSaM0buivtFUc9offApnI0Alt/K8hcHy0I=
github.com/shurcooL/notifications v0.0.0-20181007000457-627ab5aea122/go.mod h1:b5uSkrEVM1jQUspwbixRBhaIjIzL2xazXp6kntxYle0=
github.com/shurcooL/octicon v0.0.0-20181028054416-fa4f57f9efb2/go.mod h1:eWdoE5JD4R5UVWDucdOPg1g2fqQRq78IQa9zlOV1vpQ=
github.com/shurcooL/reactions v0.0.0-20181006231557-f2e0b4ca5b82/go.mod h1:TCR1lToEk4d2s07G3XGfz2QrgHXg4RJBvjrOozvoWfk=
github.com/shurcooL/sanitized_anchor_name v0.0.0-20170918181015-86672fcb3f95/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
github.com/shurcooL/users v0.0.0-20180125191416-49c67e49c537/go.mod h1:QJTqeLYEDaXHZDBsXlPCDqdhQuJkuw4NOtaxYe3xii4=
github.com/shurcooL/webdavfs v0.0.0-20170829043945-18c3829fa133/go.mod h1:hKmq5kWdCj2z2KEozexVbfEZIWiTjhE0+UjmZgPqehw=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
github.com/smartystreets/goconvey v0.0.0-20181108003508-044398e4856c/go.mod h1:XDJAKZRPZ1CvBcN2aX5YOUTYGHki24fSF0Iv48Ibg0s=
github.com/sourcegraph/annotate v0.0.0-20160123013949-f4cad6c6324d/go.mod h1:UdhH50NIW0fCiwBSr0co2m7BnFLdv4fQTgdqdJTHFeE=
github.com/sourcegraph/syntaxhighlight v0.0.0-20170531221838-bd320f5d308e/go.mod h1:HuIsMU8RRBOtsCgI77wP899iHVBQpCmg4ErYMZB+2IA=
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/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cbyO7IOYJZWg1U88JhDg3PB6klq9Hg2pA=
github.com/tencentcloud/tencentcloud-sdk-go v0.0.0-20181220135002-f1744d40d346/go.mod h1:0PfYow01SHPMhKY31xa+EFz2RStxIqj6JFAJS+IkCi4=
github.com/ugorji/go v0.0.0-20151218193438-646ae4a518c1 h1:U6ufy3mLDgg9RYupntOvAF7xCmNNquyKaYaaVHo1Nnk=
github.com/ugorji/go v0.0.0-20151218193438-646ae4a518c1/go.mod h1:hnLbHMwcvSihnDhEfx2/BzKp2xb0Y+ErdfYcrs9tkJQ=
github.com/ulikunitz/xz v0.5.5 h1:pFrO0lVpTBXLpYw+pnLj6TbvHuyjXMfjGeCwSqCVwok=
github.com/ulikunitz/xz v0.5.5/go.mod h1:2bypXElzHzzJZwzH67Y6wb67pO62Rzfn7BSiF4ABRW8=
github.com/vmware/govmomi v0.0.0-20170707011325-c2105a174311/go.mod h1:URlwyTFZX72RmxtxuaFL2Uj3fD1JTvZdx59bHWk6aFU=
github.com/vmware/govmomi v0.20.0 h1:+1IyhvoVb5JET2Wvgw9J3ZDv6CK4sxzUunpH8LhQqm4=
github.com/vmware/govmomi v0.20.0/go.mod h1:URlwyTFZX72RmxtxuaFL2Uj3fD1JTvZdx59bHWk6aFU=
github.com/xanzy/go-cloudstack v0.0.0-20190526095453-42f262b63ed0/go.mod h1:sBh287mCRwCz6zyXHMmw7sSZGPohVpnx+o+OY4M+i3A=
github.com/yandex-cloud/go-genproto v0.0.0-20190401174212-1db0ef3dce9b/go.mod h1:HEUYX/p8966tMUHHT+TsS0hF/Ca/NYwqprC5WXSDMfE=
github.com/yandex-cloud/go-sdk v0.0.0-20190402114215-3fc1d6947035/go.mod h1:Eml0jFLU4VVHgIN8zPHMuNwZXVzUMILyO6lQZSfz854=
go.opencensus.io v0.18.0/go.mod h1:vKdFvxhtzZ9onBp9VKHK8z/sRpBMnKAsufL7wlDrCOA=
go.opencensus.io v0.21.0 h1:mU6zScU4U1YAFPHEHYk+3JC4SY7JxgkqS10ZOSyksNg=
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
go.uber.org/goleak v0.10.1-0.20190517053103-3b0196519f16 h1:ucZ8P+3HTFz4/kqeQ9Mg0j8F/oHbcaWfKLWiBo3QLKk=
go.uber.org/goleak v0.10.1-0.20190517053103-3b0196519f16/go.mod h1:VCZuO8V8mFPlL0F5J5GK1rtHV3DrFcQ1R8ryq7FK0aI=
go4.org v0.0.0-20180809161055-417644f6feb5/go.mod h1:MkTOUMDaeVYJUOUsaDXIhWPZYa1yOyC1qaOBpL57BhE=
golang.org/x/build v0.0.0-20190111050920-041ab4dc3f9d/go.mod h1:OWs+y06UdEOHN4y+MfF/py+xQ/tYqIWW03b70/CG9Rw=
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-20181030102418-4d3f4d9ffa16/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-20190424203555-c05e17bb3b2d h1:adrbvkTDn9rGnXg2IJDKozEpXXLZN89pdIA+Syt4/u0=
golang.org/x/crypto v0.0.0-20190424203555-c05e17bb3b2d/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
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/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
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/mobile v0.0.0-20190607214518-6fa95d984e88 h1:H6DkDrMSuEE2MQR7DgGwkzbXSY1lvMpEN5MDE1bo/5U=
golang.org/x/mobile v0.0.0-20190607214518-6fa95d984e88/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/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-20181029044818-c44066c5c816/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181106065722-10aee1819953/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-20181220203305-927f97764cc3/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-20190125091013-d26f9f9a57f3/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-20190424112056-4829fb13d2c6 h1:FP8hkuE6yUEaJnK7O2eTuejKWwW+Rhfj80dQ2JcKxCU=
golang.org/x/net v0.0.0-20190424112056-4829fb13d2c6/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421 h1:Wo7BWFiOk0QRFMLYMqJGFMd9CgUAcGx7V+qEg/h5IBI=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/perf v0.0.0-20180704124530-6e6d33e29852/go.mod h1:JLpeXjPJfIyPr5TlbXLkXWLhP8nz10XfvxElABhCtcw=
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 h1:8gQV6CLnAEikrhgkHFbMAEhagSSnXWGV915qUMm9mrU=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/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-20181029174526-d69651ed3497/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/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-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181213200352-4d1cda033e06/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-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190425145619-16072639606e h1:4ktJgTV34+N3qOZUc5fAaG3Pb11qzMm3PkAoTAgUZ2I=
golang.org/x/sys v0.0.0-20190425145619-16072639606e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
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.1 h1:nsUiJHvm6yOoRozW9Tz0siNk9sHieLzR+w814Ihse3A=
golang.org/x/text v0.3.1/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c h1:fqgJT0MGcGpPgpWU7VRdRjuArfcOvC4AoJmILihzhDg=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/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-20181030000716-a0a13e073c7b/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-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190530184349-ce1a3806b557/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
google.golang.org/api v0.0.0-20180910000450-7ca32eb868bf/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=
google.golang.org/api v0.0.0-20181030000543-1d582fd0359e/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=
google.golang.org/api v0.1.0/go.mod h1:UGEZY7KEX120AnNLIHFMKIo4obdJhkp2tPbaPlQx13Y=
google.golang.org/api v0.4.0 h1:KKgc1aqhV8wDPbDzlDtpvyjZFY3vjz85FP7p4wcQUyI=
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.4.0 h1:/wp5JvzpHIxhs/dumFmF7BXTf3Z+dd4uXta4kVyO508=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20180831171423-11092d34479b/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20181029155118-b69ba1387ce2/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20181202183823-bd91e49a0898/go.mod h1:7Ep/1NZk928CDR8SjdVbjWNpdIf6nzjE3BTgJDr2Atg=
google.golang.org/genproto v0.0.0-20190201180003-4b09977fb922/go.mod h1:L3J43x8/uS+qIUoksaLKe6OS3nUKxOKuIFz1sl2/jx4=
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19 h1:Lj2SnHtxkRGJDqnGaSjo+CCdIieEnwVazbOXILwQemk=
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
google.golang.org/grpc v1.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio=
google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.20.1 h1:Hz2g2wirWK7H0qIIhGIqRGTuMwTE8HEKFnDZZ7lm9NU=
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
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 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/cheggaaa/pb.v1 v1.0.27 h1:kJdccidYzt3CaHD1crCFTS1hxyhSi059NhOFUf03YFo=
gopkg.in/cheggaaa/pb.v1 v1.0.27/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/h2non/gock.v1 v1.0.12/go.mod h1:KHI4Z1sxDW6P4N3DfTWSEza07YpkQP7KJBfglRMEjKY=
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
gopkg.in/ini.v1 v1.42.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/jarcoal/httpmock.v1 v1.0.0-20181117152235-275e9df93516/go.mod h1:d3R+NllX3X5e0zlG1Rful3uLvsGC/Q3OHut5464DEQw=
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
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=
grpc.go4.org v0.0.0-20170609214715-11d0a25b4919/go.mod h1:77eQGdRu53HpSqPFJFmuJdjuHRquDANNeA4x7B8WQ9o=
honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
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=
sourcegraph.com/sourcegraph/go-diff v0.5.0/go.mod h1:kuch7UrkMzY0X+p9CRK03kfuPQ2zzQcaEFbx8wA8rck=
sourcegraph.com/sqs/pbtypes v0.0.0-20180604144634-d3ebe8f20ae4/go.mod h1:ketZ/q3QxT9HOBeFhu6RdvsftgpsbFHBF5Cas6cDKZ0=

14
builder/vsphere/gofmt.sh Executable file
View File

@ -0,0 +1,14 @@
#!/bin/sh
RETVAL=0
for file in $(find . -name '*.go' -not -path './build/*')
do
if [ -n "$(gofmt -l $file)" ]
then
echo "$file does not conform to gofmt rules. Run: gofmt -s -w $file" >&2
RETVAL=1
fi
done
exit $RETVAL

View File

@ -0,0 +1,146 @@
package iso
import (
"context"
packerCommon "github.com/hashicorp/packer/common"
"github.com/hashicorp/packer/helper/communicator"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
"github.com/jetbrains-infra/packer-builder-vsphere/common"
"github.com/jetbrains-infra/packer-builder-vsphere/driver"
)
type Builder struct {
config *Config
runner multistep.Runner
}
func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
c, warnings, errs := NewConfig(raws...)
if errs != nil {
return warnings, errs
}
b.config = c
return warnings, nil
}
func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (packer.Artifact, error) {
state := new(multistep.BasicStateBag)
state.Put("comm", &b.config.Comm)
state.Put("hook", hook)
state.Put("ui", ui)
var steps []multistep.Step
steps = append(steps,
&common.StepConnect{
Config: &b.config.ConnectConfig,
},
)
if b.config.ISOUrls != nil {
steps = append(steps,
&packerCommon.StepDownload{
Checksum: b.config.ISOChecksum,
ChecksumType: b.config.ISOChecksumType,
Description: "ISO",
Extension: b.config.TargetExtension,
ResultKey: "iso_path",
TargetPath: b.config.TargetPath,
Url: b.config.ISOUrls,
},
&StepRemoteUpload{
Datastore: b.config.Datastore,
Host: b.config.Host,
},
)
}
steps = append(steps,
&StepCreateVM{
Config: &b.config.CreateConfig,
Location: &b.config.LocationConfig,
Force: b.config.PackerConfig.PackerForce,
},
&common.StepConfigureHardware{
Config: &b.config.HardwareConfig,
},
&StepAddCDRom{
Config: &b.config.CDRomConfig,
},
&common.StepConfigParams{
Config: &b.config.ConfigParamsConfig,
},
)
if b.config.Comm.Type != "none" {
steps = append(steps,
&packerCommon.StepCreateFloppy{
Files: b.config.FloppyFiles,
Directories: b.config.FloppyDirectories,
},
&StepAddFloppy{
Config: &b.config.FloppyConfig,
Datastore: b.config.Datastore,
Host: b.config.Host,
},
&packerCommon.StepHTTPServer{
HTTPDir: b.config.HTTPDir,
HTTPPortMin: b.config.HTTPPortMin,
HTTPPortMax: b.config.HTTPPortMax,
},
&common.StepRun{
Config: &b.config.RunConfig,
SetOrder: true,
},
&StepBootCommand{
Config: &b.config.BootConfig,
Ctx: b.config.ctx,
VMName: b.config.VMName,
},
&common.StepWaitForIp{
Config: &b.config.WaitIpConfig,
},
&communicator.StepConnect{
Config: &b.config.Comm,
Host: common.CommHost(b.config.Comm.SSHHost),
SSHConfig: b.config.Comm.SSHConfigFunc(),
},
&packerCommon.StepProvision{},
&common.StepShutdown{
Config: &b.config.ShutdownConfig,
},
&StepRemoveFloppy{
Datastore: b.config.Datastore,
Host: b.config.Host,
},
)
}
steps = append(steps,
&StepRemoveCDRom{},
&common.StepCreateSnapshot{
CreateSnapshot: b.config.CreateSnapshot,
},
&common.StepConvertToTemplate{
ConvertToTemplate: b.config.ConvertToTemplate,
},
)
b.runner = packerCommon.NewRunner(steps, b.config.PackerConfig, ui)
b.runner.Run(ctx, state)
if rawErr, ok := state.GetOk("error"); ok {
return nil, rawErr.(error)
}
if _, ok := state.GetOk("vm"); !ok {
return nil, nil
}
artifact := &common.Artifact{
Name: b.config.VMName,
VM: state.Get("vm").(*driver.VirtualMachine),
}
return artifact, nil
}

View File

@ -0,0 +1,555 @@
package iso
import (
"fmt"
builderT "github.com/hashicorp/packer/helper/builder/testing"
"github.com/hashicorp/packer/packer"
commonT "github.com/jetbrains-infra/packer-builder-vsphere/common/testing"
"github.com/vmware/govmomi/vim25/types"
"io/ioutil"
"os"
"testing"
)
func TestISOBuilderAcc_default(t *testing.T) {
config := defaultConfig()
builderT.Test(t, builderT.TestCase{
Builder: &Builder{},
Template: commonT.RenderConfig(config),
Check: checkDefault(t, config["vm_name"].(string), config["host"].(string), "datastore1"),
})
}
func defaultConfig() map[string]interface{} {
username := os.Getenv("VSPHERE_USERNAME")
if username == "" {
username = "root"
}
password := os.Getenv("VSPHERE_PASSWORD")
if password == "" {
password = "jetbrains"
}
config := map[string]interface{}{
"vcenter_server": "vcenter.vsphere65.test",
"username": username,
"password": password,
"insecure_connection": true,
"host": "esxi-1.vsphere65.test",
"ssh_username": "root",
"ssh_password": "jetbrains",
"vm_name": commonT.NewVMName(),
"disk_size": 2048,
"communicator": "none", // do not start the VM without any bootable devices
}
return config
}
func checkDefault(t *testing.T, name string, host string, datastore string) builderT.TestCheckFunc {
return func(artifacts []packer.Artifact) error {
d := commonT.TestConn(t)
vm := commonT.GetVM(t, d, artifacts)
vmInfo, err := vm.Info("name", "parent", "runtime.host", "resourcePool", "datastore", "layoutEx.disk", "config.firmware")
if err != nil {
t.Fatalf("Cannot read VM properties: %v", err)
}
if vmInfo.Name != name {
t.Errorf("Invalid VM name: expected '%v', got '%v'", name, vmInfo.Name)
}
f := d.NewFolder(vmInfo.Parent)
folderPath, err := f.Path()
if err != nil {
t.Fatalf("Cannot read folder name: %v", err)
}
if folderPath != "" {
t.Errorf("Invalid folder: expected '/', got '%v'", folderPath)
}
h := d.NewHost(vmInfo.Runtime.Host)
hostInfo, err := h.Info("name")
if err != nil {
t.Fatal("Cannot read host properties: ", err)
}
if hostInfo.Name != host {
t.Errorf("Invalid host name: expected '%v', got '%v'", host, hostInfo.Name)
}
p := d.NewResourcePool(vmInfo.ResourcePool)
poolPath, err := p.Path()
if err != nil {
t.Fatalf("Cannot read resource pool name: %v", err)
}
if poolPath != "" {
t.Errorf("Invalid resource pool: expected '/', got '%v'", poolPath)
}
dsr := vmInfo.Datastore[0].Reference()
ds := d.NewDatastore(&dsr)
dsInfo, err := ds.Info("name")
if err != nil {
t.Fatal("Cannot read datastore properties: ", err)
}
if dsInfo.Name != datastore {
t.Errorf("Invalid datastore name: expected '%v', got '%v'", datastore, dsInfo.Name)
}
fw := vmInfo.Config.Firmware
if fw != "bios" {
t.Errorf("Invalid firmware: expected 'bios', got '%v'", fw)
}
return nil
}
}
func TestISOBuilderAcc_notes(t *testing.T) {
builderT.Test(t, builderT.TestCase{
Builder: &Builder{},
Template: notesConfig(),
Check: checkNotes(t),
})
}
func notesConfig() string {
config := defaultConfig()
config["notes"] = "test"
return commonT.RenderConfig(config)
}
func checkNotes(t *testing.T) builderT.TestCheckFunc {
return func(artifacts []packer.Artifact) error {
d := commonT.TestConn(t)
vm := commonT.GetVM(t, d, artifacts)
vmInfo, err := vm.Info("config.annotation")
if err != nil {
t.Fatalf("Cannot read VM properties: %v", err)
}
notes := vmInfo.Config.Annotation
if notes != "test" {
t.Errorf("notes should be 'test'")
}
return nil
}
}
func TestISOBuilderAcc_hardware(t *testing.T) {
builderT.Test(t, builderT.TestCase{
Builder: &Builder{},
Template: hardwareConfig(),
Check: checkHardware(t),
})
}
func hardwareConfig() string {
config := defaultConfig()
config["CPUs"] = 2
config["cpu_cores"] = 2
config["CPU_reservation"] = 1000
config["CPU_limit"] = 1500
config["RAM"] = 2048
config["RAM_reservation"] = 1024
config["NestedHV"] = true
config["firmware"] = "efi"
config["video_ram"] = 8192
return commonT.RenderConfig(config)
}
func checkHardware(t *testing.T) builderT.TestCheckFunc {
return func(artifacts []packer.Artifact) error {
d := commonT.TestConn(t)
vm := commonT.GetVM(t, d, artifacts)
vmInfo, err := vm.Info("config")
if err != nil {
t.Fatalf("Cannot read VM properties: %v", err)
}
cpuSockets := vmInfo.Config.Hardware.NumCPU
if cpuSockets != 2 {
t.Errorf("VM should have 2 CPU sockets, got %v", cpuSockets)
}
cpuCores := vmInfo.Config.Hardware.NumCoresPerSocket
if cpuCores != 2 {
t.Errorf("VM should have 2 CPU cores per socket, got %v", cpuCores)
}
cpuReservation := *vmInfo.Config.CpuAllocation.Reservation
if cpuReservation != 1000 {
t.Errorf("VM should have CPU reservation for 1000 Mhz, got %v", cpuReservation)
}
cpuLimit := *vmInfo.Config.CpuAllocation.Limit
if cpuLimit != 1500 {
t.Errorf("VM should have CPU reservation for 1500 Mhz, got %v", cpuLimit)
}
ram := vmInfo.Config.Hardware.MemoryMB
if ram != 2048 {
t.Errorf("VM should have 2048 MB of RAM, got %v", ram)
}
ramReservation := *vmInfo.Config.MemoryAllocation.Reservation
if ramReservation != 1024 {
t.Errorf("VM should have RAM reservation for 1024 MB, got %v", ramReservation)
}
nestedHV := vmInfo.Config.NestedHVEnabled
if !*nestedHV {
t.Errorf("VM should have NestedHV enabled, got %v", nestedHV)
}
fw := vmInfo.Config.Firmware
if fw != "efi" {
t.Errorf("Invalid firmware: expected 'efi', got '%v'", fw)
}
l, err := vm.Devices()
if err != nil {
t.Fatalf("Cannot read VM devices: %v", err)
}
c := l.PickController((*types.VirtualIDEController)(nil))
if c == nil {
t.Errorf("VM should have IDE controller")
}
s := l.PickController((*types.VirtualAHCIController)(nil))
if s != nil {
t.Errorf("VM should have no SATA controllers")
}
v := l.SelectByType((*types.VirtualMachineVideoCard)(nil))
if len(v) != 1 {
t.Errorf("VM should have one video card")
}
if v[0].(*types.VirtualMachineVideoCard).VideoRamSizeInKB != 8192 {
t.Errorf("Video RAM should be equal 8192")
}
return nil
}
}
func TestISOBuilderAcc_limit(t *testing.T) {
builderT.Test(t, builderT.TestCase{
Builder: &Builder{},
Template: limitConfig(),
Check: checkLimit(t),
})
}
func limitConfig() string {
config := defaultConfig()
config["CPUs"] = 1 // hardware is customized, but CPU limit is not specified explicitly
return commonT.RenderConfig(config)
}
func checkLimit(t *testing.T) builderT.TestCheckFunc {
return func(artifacts []packer.Artifact) error {
d := commonT.TestConn(t)
vm := commonT.GetVM(t, d, artifacts)
vmInfo, err := vm.Info("config.cpuAllocation")
if err != nil {
t.Fatalf("Cannot read VM properties: %v", err)
}
limit := *vmInfo.Config.CpuAllocation.Limit
if limit != -1 { // must be unlimited
t.Errorf("Invalid CPU limit: expected '%v', got '%v'", -1, limit)
}
return nil
}
}
func TestISOBuilderAcc_sata(t *testing.T) {
builderT.Test(t, builderT.TestCase{
Builder: &Builder{},
Template: sataConfig(),
Check: checkSata(t),
})
}
func sataConfig() string {
config := defaultConfig()
config["cdrom_type"] = "sata"
return commonT.RenderConfig(config)
}
func checkSata(t *testing.T) builderT.TestCheckFunc {
return func(artifacts []packer.Artifact) error {
d := commonT.TestConn(t)
vm := commonT.GetVM(t, d, artifacts)
l, err := vm.Devices()
if err != nil {
t.Fatalf("Cannot read VM devices: %v", err)
}
c := l.PickController((*types.VirtualAHCIController)(nil))
if c == nil {
t.Errorf("VM has no SATA controllers")
}
return nil
}
}
func TestISOBuilderAcc_cdrom(t *testing.T) {
builderT.Test(t, builderT.TestCase{
Builder: &Builder{},
Template: cdromConfig(),
})
}
func cdromConfig() string {
config := defaultConfig()
config["iso_paths"] = []string{
"[datastore1] test0.iso",
"[datastore1] test1.iso",
}
return commonT.RenderConfig(config)
}
func TestISOBuilderAcc_networkCard(t *testing.T) {
builderT.Test(t, builderT.TestCase{
Builder: &Builder{},
Template: networkCardConfig(),
Check: checkNetworkCard(t),
})
}
func networkCardConfig() string {
config := defaultConfig()
config["network_card"] = "vmxnet3"
return commonT.RenderConfig(config)
}
func checkNetworkCard(t *testing.T) builderT.TestCheckFunc {
return func(artifacts []packer.Artifact) error {
d := commonT.TestConn(t)
vm := commonT.GetVM(t, d, artifacts)
devices, err := vm.Devices()
if err != nil {
t.Fatalf("Cannot read VM properties: %v", err)
}
netCards := devices.SelectByType((*types.VirtualEthernetCard)(nil))
if len(netCards) == 0 {
t.Fatalf("Cannot find the network card")
}
if len(netCards) > 1 {
t.Fatalf("Found several network catds")
}
if _, ok := netCards[0].(*types.VirtualVmxnet3); !ok {
t.Errorf("The network card type is not the expected one (vmxnet3)")
}
return nil
}
}
func TestISOBuilderAcc_createFloppy(t *testing.T) {
tmpFile, err := ioutil.TempFile("", "packer-vsphere-iso-test")
if err != nil {
t.Fatalf("Error creating temp file: %v", err)
}
_, err = fmt.Fprint(tmpFile, "Hello, World!")
if err != nil {
t.Fatalf("Error creating temp file: %v", err)
}
err = tmpFile.Close()
if err != nil {
t.Fatalf("Error creating temp file: %v", err)
}
builderT.Test(t, builderT.TestCase{
Builder: &Builder{},
Template: createFloppyConfig(tmpFile.Name()),
})
}
func createFloppyConfig(filePath string) string {
config := defaultConfig()
config["floppy_files"] = []string{filePath}
return commonT.RenderConfig(config)
}
func TestISOBuilderAcc_full(t *testing.T) {
config := fullConfig()
builderT.Test(t, builderT.TestCase{
Builder: &Builder{},
Template: commonT.RenderConfig(config),
Check: checkFull(t),
})
}
func fullConfig() map[string]interface{} {
username := os.Getenv("VSPHERE_USERNAME")
if username == "" {
username = "root"
}
password := os.Getenv("VSPHERE_PASSWORD")
if password == "" {
password = "jetbrains"
}
config := map[string]interface{}{
"vcenter_server": "vcenter.vsphere65.test",
"username": username,
"password": password,
"insecure_connection": true,
"vm_name": commonT.NewVMName(),
"host": "esxi-1.vsphere65.test",
"RAM": 512,
"disk_controller_type": "pvscsi",
"disk_size": 1024,
"disk_thin_provisioned": true,
"network_card": "vmxnet3",
"guest_os_type": "other3xLinux64Guest",
"iso_paths": []string{
"[datastore1] ISO/alpine-standard-3.8.2-x86_64.iso",
},
"floppy_files": []string{
"../examples/alpine/answerfile",
"../examples/alpine/setup.sh",
},
"boot_wait": "20s",
"boot_command": []string{
"root<enter><wait>",
"mount -t vfat /dev/fd0 /media/floppy<enter><wait>",
"setup-alpine -f /media/floppy/answerfile<enter>",
"<wait5>",
"jetbrains<enter>",
"jetbrains<enter>",
"<wait5>",
"y<enter>",
"<wait10><wait10><wait10><wait10>",
"reboot<enter>",
"<wait10><wait10><wait10>",
"root<enter>",
"jetbrains<enter><wait>",
"mount -t vfat /dev/fd0 /media/floppy<enter><wait>",
"/media/floppy/SETUP.SH<enter>",
},
"ssh_username": "root",
"ssh_password": "jetbrains",
}
return config
}
func checkFull(t *testing.T) builderT.TestCheckFunc {
return func(artifacts []packer.Artifact) error {
d := commonT.TestConn(t)
vm := commonT.GetVM(t, d, artifacts)
vmInfo, err := vm.Info("config.bootOptions")
if err != nil {
t.Fatalf("Cannot read VM properties: %v", err)
}
order := vmInfo.Config.BootOptions.BootOrder
if order != nil {
t.Errorf("Boot order must be empty")
}
devices, err := vm.Devices()
if err != nil {
t.Fatalf("Cannot read devices: %v", err)
}
cdroms := devices.SelectByType((*types.VirtualCdrom)(nil))
for _, cd := range cdroms {
_, ok := cd.(*types.VirtualCdrom).Backing.(*types.VirtualCdromRemotePassthroughBackingInfo)
if !ok {
t.Errorf("wrong cdrom backing")
}
}
return nil
}
}
func TestISOBuilderAcc_bootOrder(t *testing.T) {
config := fullConfig()
config["boot_order"] = "disk,cdrom,floppy"
builderT.Test(t, builderT.TestCase{
Builder: &Builder{},
Template: commonT.RenderConfig(config),
Check: checkBootOrder(t),
})
}
func checkBootOrder(t *testing.T) builderT.TestCheckFunc {
return func(artifacts []packer.Artifact) error {
d := commonT.TestConn(t)
vm := commonT.GetVM(t, d, artifacts)
vmInfo, err := vm.Info("config.bootOptions")
if err != nil {
t.Fatalf("Cannot read VM properties: %v", err)
}
order := vmInfo.Config.BootOptions.BootOrder
if order == nil {
t.Errorf("Boot order must not be empty")
}
return nil
}
}
func TestISOBuilderAcc_cluster(t *testing.T) {
builderT.Test(t, builderT.TestCase{
Builder: &Builder{},
Template: clusterConfig(),
})
}
func clusterConfig() string {
config := defaultConfig()
config["cluster"] = "cluster1"
config["host"] = "esxi-2.vsphere65.test"
return commonT.RenderConfig(config)
}
func TestISOBuilderAcc_clusterDRS(t *testing.T) {
builderT.Test(t, builderT.TestCase{
Builder: &Builder{},
Template: clusterDRSConfig(),
})
}
func clusterDRSConfig() string {
config := defaultConfig()
config["cluster"] = "cluster2"
config["host"] = ""
config["datastore"] = "datastore3" // bug #183
config["network"] = "VM Network" // bug #183
return commonT.RenderConfig(config)
}

View File

@ -0,0 +1,80 @@
package iso
import (
packerCommon "github.com/hashicorp/packer/common"
"github.com/hashicorp/packer/helper/communicator"
"github.com/hashicorp/packer/helper/config"
"github.com/hashicorp/packer/packer"
"github.com/hashicorp/packer/template/interpolate"
"github.com/jetbrains-infra/packer-builder-vsphere/common"
)
type Config struct {
packerCommon.PackerConfig `mapstructure:",squash"`
packerCommon.HTTPConfig `mapstructure:",squash"`
common.ConnectConfig `mapstructure:",squash"`
CreateConfig `mapstructure:",squash"`
common.LocationConfig `mapstructure:",squash"`
common.HardwareConfig `mapstructure:",squash"`
common.ConfigParamsConfig `mapstructure:",squash"`
packerCommon.ISOConfig `mapstructure:",squash"`
CDRomConfig `mapstructure:",squash"`
FloppyConfig `mapstructure:",squash"`
common.RunConfig `mapstructure:",squash"`
BootConfig `mapstructure:",squash"`
common.WaitIpConfig `mapstructure:",squash"`
Comm communicator.Config `mapstructure:",squash"`
common.ShutdownConfig `mapstructure:",squash"`
CreateSnapshot bool `mapstructure:"create_snapshot"`
ConvertToTemplate bool `mapstructure:"convert_to_template"`
ctx interpolate.Context
}
func NewConfig(raws ...interface{}) (*Config, []string, error) {
c := new(Config)
err := config.Decode(c, &config.DecodeOpts{
Interpolate: true,
InterpolateContext: &c.ctx,
InterpolateFilter: &interpolate.RenderFilter{
Exclude: []string{
"boot_command",
},
},
}, raws...)
if err != nil {
return nil, nil, err
}
warnings := make([]string, 0)
errs := new(packer.MultiError)
if c.ISOUrls != nil {
isoWarnings, isoErrs := c.ISOConfig.Prepare(&c.ctx)
warnings = append(warnings, isoWarnings...)
errs = packer.MultiErrorAppend(errs, isoErrs...)
}
errs = packer.MultiErrorAppend(errs, c.ConnectConfig.Prepare()...)
errs = packer.MultiErrorAppend(errs, c.CreateConfig.Prepare()...)
errs = packer.MultiErrorAppend(errs, c.LocationConfig.Prepare()...)
errs = packer.MultiErrorAppend(errs, c.HardwareConfig.Prepare()...)
errs = packer.MultiErrorAppend(errs, c.HTTPConfig.Prepare(&c.ctx)...)
errs = packer.MultiErrorAppend(errs, c.CDRomConfig.Prepare()...)
errs = packer.MultiErrorAppend(errs, c.BootConfig.Prepare()...)
errs = packer.MultiErrorAppend(errs, c.WaitIpConfig.Prepare()...)
errs = packer.MultiErrorAppend(errs, c.Comm.Prepare(&c.ctx)...)
errs = packer.MultiErrorAppend(errs, c.ShutdownConfig.Prepare()...)
if len(errs.Errors) > 0 {
return nil, nil, errs
}
return c, nil, nil
}

View File

@ -0,0 +1,8 @@
package iso
import "testing"
import "go.uber.org/goleak"
func TestMain(m *testing.M) {
goleak.VerifyTestMain(m, goleak.IgnoreTopFunction("go.opencensus.io/stats/view.(*worker).start"))
}

View File

@ -0,0 +1,63 @@
package iso
import (
"context"
"fmt"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
"github.com/jetbrains-infra/packer-builder-vsphere/driver"
)
type CDRomConfig struct {
CdromType string `mapstructure:"cdrom_type"`
ISOPaths []string `mapstructure:"iso_paths"`
}
type StepAddCDRom struct {
Config *CDRomConfig
}
func (c *CDRomConfig) Prepare() []error {
var errs []error
if c.CdromType != "" && c.CdromType != "ide" && c.CdromType != "sata" {
errs = append(errs, fmt.Errorf("'cdrom_type' must be 'ide' or 'sata'"))
}
return errs
}
func (s *StepAddCDRom) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
ui := state.Get("ui").(packer.Ui)
vm := state.Get("vm").(*driver.VirtualMachine)
if s.Config.CdromType == "sata" {
if _, err := vm.FindSATAController(); err == driver.ErrNoSataController {
ui.Say("Adding SATA controller...")
if err := vm.AddSATAController(); err != nil {
state.Put("error", fmt.Errorf("error adding SATA controller: %v", err))
return multistep.ActionHalt
}
}
}
ui.Say("Mount ISO images...")
if len(s.Config.ISOPaths) > 0 {
for _, path := range s.Config.ISOPaths {
if err := vm.AddCdrom(s.Config.CdromType, path); err != nil {
state.Put("error", fmt.Errorf("error mounting an image '%v': %v", path, err))
return multistep.ActionHalt
}
}
}
if path, ok := state.GetOk("iso_remote_path"); ok {
if err := vm.AddCdrom(s.Config.CdromType, path.(string)); err != nil {
state.Put("error", fmt.Errorf("error mounting an image '%v': %v", path, err))
return multistep.ActionHalt
}
}
return multistep.ActionContinue
}
func (s *StepAddCDRom) Cleanup(state multistep.StateBag) {}

View File

@ -0,0 +1,96 @@
package iso
import (
"context"
"fmt"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
"github.com/jetbrains-infra/packer-builder-vsphere/driver"
)
type FloppyConfig struct {
FloppyIMGPath string `mapstructure:"floppy_img_path"`
FloppyFiles []string `mapstructure:"floppy_files"`
FloppyDirectories []string `mapstructure:"floppy_dirs"`
}
type StepAddFloppy struct {
Config *FloppyConfig
Datastore string
Host string
}
func (s *StepAddFloppy) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
ui := state.Get("ui").(packer.Ui)
vm := state.Get("vm").(*driver.VirtualMachine)
d := state.Get("driver").(*driver.Driver)
if floppyPath, ok := state.GetOk("floppy_path"); ok {
ui.Say("Uploading created floppy image")
ds, err := d.FindDatastore(s.Datastore, s.Host)
if err != nil {
state.Put("error", err)
return multistep.ActionHalt
}
vmDir, err := vm.GetDir()
if err != nil {
state.Put("error", err)
return multistep.ActionHalt
}
uploadPath := fmt.Sprintf("%v/packer-tmp-created-floppy.flp", vmDir)
if err := ds.UploadFile(floppyPath.(string), uploadPath, s.Host); err != nil {
state.Put("error", err)
return multistep.ActionHalt
}
state.Put("uploaded_floppy_path", uploadPath)
ui.Say("Adding generated Floppy...")
floppyIMGPath := ds.ResolvePath(uploadPath)
err = vm.AddFloppy(floppyIMGPath)
if err != nil {
state.Put("error", err)
return multistep.ActionHalt
}
}
if s.Config.FloppyIMGPath != "" {
ui.Say("Adding Floppy image...")
err := vm.AddFloppy(s.Config.FloppyIMGPath)
if err != nil {
state.Put("error", err)
return multistep.ActionHalt
}
}
return multistep.ActionContinue
}
func (s *StepAddFloppy) Cleanup(state multistep.StateBag) {
_, cancelled := state.GetOk(multistep.StateCancelled)
_, halted := state.GetOk(multistep.StateHalted)
if !cancelled && !halted {
return
}
ui := state.Get("ui").(packer.Ui)
d := state.Get("driver").(*driver.Driver)
if UploadedFloppyPath, ok := state.GetOk("uploaded_floppy_path"); ok {
ui.Say("Deleting Floppy image ...")
ds, err := d.FindDatastore(s.Datastore, s.Host)
if err != nil {
state.Put("error", err)
return
}
err = ds.Delete(UploadedFloppyPath.(string))
if err != nil {
state.Put("error", err)
return
}
}
}

View File

@ -0,0 +1,260 @@
package iso
import (
"context"
"fmt"
packerCommon "github.com/hashicorp/packer/common"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
"github.com/hashicorp/packer/template/interpolate"
"github.com/jetbrains-infra/packer-builder-vsphere/driver"
"golang.org/x/mobile/event/key"
"log"
"net"
"os"
"strings"
"time"
"unicode/utf8"
)
type BootConfig struct {
BootCommand []string `mapstructure:"boot_command"`
BootWait time.Duration `mapstructure:"boot_wait"` // example: "1m30s"; default: "10s"
HTTPIP string `mapstructure:"http_ip"`
}
type bootCommandTemplateData struct {
HTTPIP string
HTTPPort int
Name string
}
func (c *BootConfig) Prepare() []error {
var errs []error
if c.BootWait == 0 {
c.BootWait = 10 * time.Second
}
return errs
}
type StepBootCommand struct {
Config *BootConfig
VMName string
Ctx interpolate.Context
}
var special = map[string]key.Code{
"<enter>": key.CodeReturnEnter,
"<esc>": key.CodeEscape,
"<bs>": key.CodeDeleteBackspace,
"<del>": key.CodeDeleteForward,
"<tab>": key.CodeTab,
"<f1>": key.CodeF1,
"<f2>": key.CodeF2,
"<f3>": key.CodeF3,
"<f4>": key.CodeF4,
"<f5>": key.CodeF5,
"<f6>": key.CodeF6,
"<f7>": key.CodeF7,
"<f8>": key.CodeF8,
"<f9>": key.CodeF9,
"<f10>": key.CodeF10,
"<f11>": key.CodeF11,
"<f12>": key.CodeF12,
"<insert>": key.CodeInsert,
"<home>": key.CodeHome,
"<end>": key.CodeEnd,
"<pageUp>": key.CodePageUp,
"<pageDown>": key.CodePageDown,
"<left>": key.CodeLeftArrow,
"<right>": key.CodeRightArrow,
"<up>": key.CodeUpArrow,
"<down>": key.CodeDownArrow,
}
var keyInterval = packerCommon.PackerKeyDefault
func init() {
if delay, err := time.ParseDuration(os.Getenv(packerCommon.PackerKeyEnv)); err == nil {
keyInterval = delay
}
}
func (s *StepBootCommand) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
ui := state.Get("ui").(packer.Ui)
vm := state.Get("vm").(*driver.VirtualMachine)
if s.Config.BootCommand == nil {
return multistep.ActionContinue
}
ui.Say(fmt.Sprintf("Waiting %s for boot...", s.Config.BootWait))
wait := time.After(s.Config.BootWait)
WAITLOOP:
for {
select {
case <-wait:
break WAITLOOP
case <-time.After(1 * time.Second):
if _, ok := state.GetOk(multistep.StateCancelled); ok {
return multistep.ActionHalt
}
}
}
port := state.Get("http_port").(int)
if port > 0 {
ip, err := getHostIP(s.Config.HTTPIP)
if err != nil {
state.Put("error", err)
return multistep.ActionHalt
}
err = packerCommon.SetHTTPIP(ip)
if err != nil {
state.Put("error", err)
return multistep.ActionHalt
}
s.Ctx.Data = &bootCommandTemplateData{
ip,
port,
s.VMName,
}
ui.Say(fmt.Sprintf("HTTP server is working at http://%v:%v/", ip, port))
}
ui.Say("Typing boot command...")
var keyAlt bool
var keyCtrl bool
var keyShift bool
for _, command := range s.Config.BootCommand {
message, err := interpolate.Render(command, &s.Ctx)
if err != nil {
state.Put("error", err)
return multistep.ActionHalt
}
for len(message) > 0 {
if _, ok := state.GetOk(multistep.StateCancelled); ok {
return multistep.ActionHalt
}
if strings.HasPrefix(message, "<wait>") {
log.Printf("Waiting 1 second")
time.Sleep(1 * time.Second)
message = message[len("<wait>"):]
continue
}
if strings.HasPrefix(message, "<wait5>") {
log.Printf("Waiting 5 seconds")
time.Sleep(5 * time.Second)
message = message[len("<wait5>"):]
continue
}
if strings.HasPrefix(message, "<wait10>") {
log.Printf("Waiting 10 seconds")
time.Sleep(10 * time.Second)
message = message[len("<wait10>"):]
continue
}
if strings.HasPrefix(message, "<leftAltOn>") {
keyAlt = true
message = message[len("<leftAltOn>"):]
continue
}
if strings.HasPrefix(message, "<leftAltOff>") {
keyAlt = false
message = message[len("<leftAltOff>"):]
continue
}
if strings.HasPrefix(message, "<leftCtrlOn>") {
keyCtrl = true
message = message[len("<leftCtrlOn>"):]
continue
}
if strings.HasPrefix(message, "<leftCtrlOff>") {
keyCtrl = false
message = message[len("<leftCtrlOff>"):]
continue
}
if strings.HasPrefix(message, "<leftShiftOn>") {
keyShift = true
message = message[len("<leftShiftOn>"):]
continue
}
if strings.HasPrefix(message, "<leftShiftOff>") {
keyShift = false
message = message[len("<leftShiftOff>"):]
continue
}
var scancode key.Code
for specialCode, specialValue := range special {
if strings.HasPrefix(message, specialCode) {
scancode = specialValue
log.Printf("Special code '%s' found, replacing with: %s", specialCode, specialValue)
message = message[len(specialCode):]
}
}
var char rune
if scancode == 0 {
var size int
char, size = utf8.DecodeRuneInString(message)
message = message[size:]
}
_, err := vm.TypeOnKeyboard(driver.KeyInput{
Message: string(char),
Scancode: scancode,
Ctrl: keyCtrl,
Alt: keyAlt,
Shift: keyShift,
})
if err != nil {
state.Put("error", fmt.Errorf("error typing a boot command: %v", err))
return multistep.ActionHalt
}
time.Sleep(keyInterval)
}
}
return multistep.ActionContinue
}
func (s *StepBootCommand) Cleanup(state multistep.StateBag) {}
func getHostIP(s string) (string, error) {
if s != "" {
if net.ParseIP(s) != nil {
return s, nil
} else {
return "", fmt.Errorf("invalid IP address")
}
}
addrs, err := net.InterfaceAddrs()
if err != nil {
return "", err
}
for _, a := range addrs {
ipnet, ok := a.(*net.IPNet)
if ok && !ipnet.IP.IsLoopback() {
if ipnet.IP.To4() != nil {
return ipnet.IP.String(), nil
}
}
}
return "", fmt.Errorf("IP not found")
}

View File

@ -0,0 +1,117 @@
package iso
import (
"context"
"fmt"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
"github.com/jetbrains-infra/packer-builder-vsphere/common"
"github.com/jetbrains-infra/packer-builder-vsphere/driver"
)
type CreateConfig struct {
Version uint `mapstructure:"vm_version"`
GuestOSType string `mapstructure:"guest_os_type"`
Firmware string `mapstructure:"firmware"`
DiskControllerType string `mapstructure:"disk_controller_type"`
DiskSize int64 `mapstructure:"disk_size"`
DiskThinProvisioned bool `mapstructure:"disk_thin_provisioned"`
Network string `mapstructure:"network"`
NetworkCard string `mapstructure:"network_card"`
USBController bool `mapstructure:"usb_controller"`
Notes string `mapstructure:"notes"`
}
func (c *CreateConfig) Prepare() []error {
var errs []error
if c.DiskSize == 0 {
errs = append(errs, fmt.Errorf("'disk_size' is required"))
}
if c.GuestOSType == "" {
c.GuestOSType = "otherGuest"
}
if c.Firmware != "" && c.Firmware != "bios" && c.Firmware != "efi" {
errs = append(errs, fmt.Errorf("'firmware' must be 'bios' or 'efi'"))
}
return errs
}
type StepCreateVM struct {
Config *CreateConfig
Location *common.LocationConfig
Force bool
}
func (s *StepCreateVM) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
ui := state.Get("ui").(packer.Ui)
d := state.Get("driver").(*driver.Driver)
vm, err := d.FindVM(s.Location.VMName)
if s.Force == false && err == nil {
state.Put("error", fmt.Errorf("%s already exists, you can use -force flag to destroy it: %v", s.Location.VMName, err))
return multistep.ActionHalt
} else if s.Force == true && err == nil {
ui.Say(fmt.Sprintf("the vm/template %s already exists, but deleting it due to -force flag", s.Location.VMName))
err := vm.Destroy()
if err != nil {
state.Put("error", fmt.Errorf("error destroying %s: %v", s.Location.VMName, err))
}
}
ui.Say("Creating VM...")
vm, err = d.CreateVM(&driver.CreateConfig{
DiskThinProvisioned: s.Config.DiskThinProvisioned,
DiskControllerType: s.Config.DiskControllerType,
DiskSize: s.Config.DiskSize,
Name: s.Location.VMName,
Folder: s.Location.Folder,
Cluster: s.Location.Cluster,
Host: s.Location.Host,
ResourcePool: s.Location.ResourcePool,
Datastore: s.Location.Datastore,
GuestOS: s.Config.GuestOSType,
Network: s.Config.Network,
NetworkCard: s.Config.NetworkCard,
USBController: s.Config.USBController,
Version: s.Config.Version,
Firmware: s.Config.Firmware,
Annotation: s.Config.Notes,
})
if err != nil {
state.Put("error", fmt.Errorf("error creating vm: %v", err))
return multistep.ActionHalt
}
state.Put("vm", vm)
return multistep.ActionContinue
}
func (s *StepCreateVM) Cleanup(state multistep.StateBag) {
_, cancelled := state.GetOk(multistep.StateCancelled)
_, halted := state.GetOk(multistep.StateHalted)
if !cancelled && !halted {
return
}
ui := state.Get("ui").(packer.Ui)
st := state.Get("vm")
if st == nil {
return
}
vm := st.(*driver.VirtualMachine)
ui.Say("Destroying VM...")
err := vm.Destroy()
if err != nil {
ui.Error(err.Error())
}
}

View File

@ -0,0 +1,57 @@
package iso
import (
"context"
"fmt"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
"github.com/jetbrains-infra/packer-builder-vsphere/driver"
"path/filepath"
)
type StepRemoteUpload struct {
Datastore string
Host string
}
func (s *StepRemoteUpload) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
ui := state.Get("ui").(packer.Ui)
d := state.Get("driver").(*driver.Driver)
if path, ok := state.GetOk("iso_path"); ok {
filename := filepath.Base(path.(string))
ds, err := d.FindDatastore(s.Datastore, s.Host)
if err != nil {
state.Put("error", fmt.Errorf("datastore doesn't exist: %v", err))
return multistep.ActionHalt
}
remotePath := fmt.Sprintf("packer_cache/%s", filename)
remoteDirectory := fmt.Sprintf("[%s] packer_cache/", ds.Name())
fullRemotePath := fmt.Sprintf("%s/%s", remoteDirectory, filename)
ui.Say(fmt.Sprintf("Uploading %s to %s", filename, remotePath))
if exists := ds.FileExists(remotePath); exists == true {
ui.Say("File already upload")
state.Put("iso_remote_path", fullRemotePath)
return multistep.ActionContinue
}
if err := ds.MakeDirectory(remoteDirectory); err != nil {
state.Put("error", err)
return multistep.ActionHalt
}
if err := ds.UploadFile(path.(string), remotePath, s.Host); err != nil {
state.Put("error", err)
return multistep.ActionHalt
}
state.Put("iso_remote_path", fullRemotePath)
}
return multistep.ActionContinue
}
func (s *StepRemoteUpload) Cleanup(state multistep.StateBag) {}

View File

@ -0,0 +1,26 @@
package iso
import (
"context"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
"github.com/jetbrains-infra/packer-builder-vsphere/driver"
)
type StepRemoveCDRom struct{}
func (s *StepRemoveCDRom) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
ui := state.Get("ui").(packer.Ui)
vm := state.Get("vm").(*driver.VirtualMachine)
ui.Say("Eject CD-ROM drives...")
err := vm.EjectCdroms()
if err != nil {
state.Put("error", err)
return multistep.ActionHalt
}
return multistep.ActionContinue
}
func (s *StepRemoveCDRom) Cleanup(state multistep.StateBag) {}

View File

@ -0,0 +1,49 @@
package iso
import (
"context"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
"github.com/jetbrains-infra/packer-builder-vsphere/driver"
"github.com/vmware/govmomi/vim25/types"
)
type StepRemoveFloppy struct {
Datastore string
Host string
}
func (s *StepRemoveFloppy) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
ui := state.Get("ui").(packer.Ui)
vm := state.Get("vm").(*driver.VirtualMachine)
d := state.Get("driver").(*driver.Driver)
ui.Say("Deleting Floppy drives...")
devices, err := vm.Devices()
if err != nil {
state.Put("error", err)
return multistep.ActionHalt
}
floppies := devices.SelectByType((*types.VirtualFloppy)(nil))
if err = vm.RemoveDevice(true, floppies...); err != nil {
state.Put("error", err)
return multistep.ActionHalt
}
if UploadedFloppyPath, ok := state.GetOk("uploaded_floppy_path"); ok {
ui.Say("Deleting Floppy image...")
ds, err := d.FindDatastore(s.Datastore, s.Host)
if err != nil {
state.Put("error", err)
return multistep.ActionHalt
}
if err := ds.Delete(UploadedFloppyPath.(string)); err != nil {
state.Put("error", err)
return multistep.ActionHalt
}
}
return multistep.ActionContinue
}
func (s *StepRemoveFloppy) Cleanup(state multistep.StateBag) {}

View File

@ -0,0 +1,15 @@
version: '2'
services:
vpn:
container_name: vpn
image: jetbrainsinfra/openvpn
volumes:
- ./test:/vpn:ro
cap_add:
- NET_ADMIN
devices:
- /dev/net/tun:/dev/net/tun
dns: 10.0.0.1
environment:
- VPN_PASSWORD
entrypoint: "sh -c 'echo $$VPN_PASSWORD | openvpn --cd /vpn/ --config lab.ovpn --askpass /dev/stdin'"

View File

@ -0,0 +1,38 @@
dev tun
persist-tun
persist-key
cipher AES-256-CBC
ncp-ciphers AES-256-GCM:AES-128-GCM
auth SHA1
tls-client
client
resolv-retry infinite
remote 91.132.204.28 2000 tcp-client
remote-cert-tls server
pkcs12 lab.p12
<tls-auth>
#
# 2048 bit OpenVPN static key
#
-----BEGIN OpenVPN Static key V1-----
6c9efab783fc2ee1a558bcedeaf92f8d
85322bc05432fbb00745fcd00bb48857
77cbf0c82462726a848657c56b62f6fd
b9b1622c633188e848ce78c1b4476e9f
938338532c79784f36d80156e3b29bcf
493e64c393ee216b776c7a5d62c03aa8
5fc5fea73990612f07660988da133b61
34c847e67f65b8af407ae0b2761de402
49ede990747659a878acaaf8fa1a6201
1aa8ec5aeb01ccf50d1dc6e675dea291
8d4c199c1c126fee9c112ce16c736159
3234d5eaea167f5e60d01ad618fd33bb
c262fb3d5227933d6149e45ab0246d58
5f5d66d835fbfc8e8d51e0462194d835
8f66f166ccef5616abba26dd38046a87
9476359e2dc7a5b4dc045e3fbe39d6e6
-----END OpenVPN Static key V1-----
</tls-auth>
key-direction 1

Binary file not shown.

View File

@ -0,0 +1,27 @@
-----BEGIN RSA PRIVATE KEY-----
MIIEowIBAAKCAQEA2J9w3cbqMJSDTCUtFW3qRHhqgXbSOW32anqEWQYvW48WKXJm
ZmuuSViC0tcAMCnX8pu5YGlAMCi5RBDtdoE9mZzUCfE4Q1Om42S2jKRrSSbhU9Ts
8jTRL0V81Tja64SEt5l1dDHS5sgNJy8C4nWaWob1HT+YloPEllj80ogwoQoL3ufp
r5me/TOrA3ApHXewWm0feBkkkuN6NkL1Z9sILCstLrjD+RVEOvI/wrHZEaLpYJ4P
LgS8LmTNKaFafmqwgcC4VcA4kVbhxw9X385v+mQLqpiOJa+vS51dT2qINEw+80Y+
HL7k7OIZTLg803wubI3rUZQ/2PX/STBq1zO9RwIDAQABAoIBAAmrDBGJ6Dfk2PtU
CXAUaMlHipFeqUFQ7BeSgkeq5AA1IasV5QYbNjslzSj12ZdMtsuoMZzg9bFwj9w+
2SpZ2FL70ebjsjwnBqLNguxCBlvMdXAVZ8Hjo5Z1hn3JvNOYJYhAPCLEeoI8WYHv
MjTDRPFXZqc4iGnnVaXUMOyAkZMOV6sMQzvuJad4x7gvQGRhCgcdnFdGbVs+MZQc
WPI6cO6imj27F6rJK3W6s5XcSjDbkpytf2wUuWYgck93Fdm3kYy3ER6B3P/MiM95
qGRmg6OuEYbXAr4ytamjKUThl83SGvDS89N5SIjS5rgrEBgrOFBgMhjG/ibaxbrh
c84oplECgYEA+vyI4VUYgce8voYmdDijlM/NwPbCpD3SGiyXIYcDN1i/CUdDhBYh
z4982H6I1b2cg+veBWICro9Dp20CpfGtXT6Y3o1yNWkbKlosd+f2Us10fG1gkcyI
TiZCYaJPrtdoTT0vMKbdUbkgn0FLNbW1TCh5FQ7K7RXhDonb9BbsTzkCgYEA3PMu
bv/MgaET654GAItudazJmh4FfR905w59yVNJfe+7iG/f5zzv7vIpaERvBo245hcu
IaO8QbW5OKYuCaNIjGOSd1uxN5ytcOHcf1bmjS+WRQdu/FR5v9BM0BY66NFjqKMb
dZLXVZPnU3EOqCKmi9SI2VOVKrDL5XzMOHhL8H8CgYBFJh5wNomx993AgCVID/LB
pR8C8vldVsrz+yUIT7JLJWA8pi2rzo0yKk4zN2lrufnNPsbEpOQoQ8BX+GiqX5Ns
BTsI1d+JZ5Pcb0uhHX94ALL/NQNOKBPFtDTFwXpCqYZLAXhm5xJC2cZrGgommhGB
EgWKD7FI8KY44zJ+ZXJlwQKBgGvw/eFKZI17tPCp3cLMW2VvyXnaatIK2SC8SqVd
ZAz7XoG0Lg2ZDpqMgcAnlpn8CLWX43iZtjHf5qIPRXR96cZ0KqzXBcfmajE4lnE7
chzNf7sve4AYgPY9fBk4kwUEroxHSvXwi/SJ8jwogoGPlA/CAC00ES6u+p2dj2OT
GX5fAoGBAM6saTeyjAjLDE/vlPM9OButsoj5CJg7DklRgrRuRyygbyRBudafslnl
8e4+4mlXEBwKDnrDTtXFhX1Ur95/w/4GjyFXO/TB/Tmn+vaEBQTzgViKc2cJ/yay
ttiF6oJh9EjCaFDTz5P11wX7DajRux/2tUcBXX/C3FcGhNEkVb2P
-----END RSA PRIVATE KEY-----

View File

@ -0,0 +1 @@
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDYn3DdxuowlINMJS0VbepEeGqBdtI5bfZqeoRZBi9bjxYpcmZma65JWILS1wAwKdfym7lgaUAwKLlEEO12gT2ZnNQJ8ThDU6bjZLaMpGtJJuFT1OzyNNEvRXzVONrrhIS3mXV0MdLmyA0nLwLidZpahvUdP5iWg8SWWPzSiDChCgve5+mvmZ79M6sDcCkdd7BabR94GSSS43o2QvVn2wgsKy0uuMP5FUQ68j/CsdkRoulgng8uBLwuZM0poVp+arCBwLhVwDiRVuHHD1ffzm/6ZAuqmI4lr69LnV1Paog0TD7zRj4cvuTs4hlMuDzTfC5sjetRlD/Y9f9JMGrXM71H