Merge remote-tracking branch 'packer-builder-vsphere/master' into cd/merge
This commit is contained in:
commit
1140504935
|
@ -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>
|
|
@ -0,0 +1,2 @@
|
|||
*.iml
|
||||
target/
|
|
@ -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>
|
|
@ -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
|
||||
})
|
|
@ -0,0 +1,9 @@
|
|||
.idea/
|
||||
packer-builder-vsphere*
|
||||
build/
|
||||
bin/
|
||||
.env
|
||||
test*.json
|
||||
crash.log
|
||||
packer_cache/
|
||||
vendor/
|
|
@ -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.
|
|
@ -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
|
|
@ -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
|
||||
```
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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")
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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"))
|
||||
}
|
|
@ -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())
|
||||
}
|
||||
}
|
|
@ -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()
|
||||
}
|
|
@ -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()
|
||||
}
|
|
@ -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()
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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) {}
|
|
@ -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) {}
|
|
@ -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) {}
|
|
@ -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())
|
||||
}
|
||||
}
|
|
@ -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) {}
|
|
@ -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) {}
|
|
@ -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) {}
|
|
@ -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) {}
|
|
@ -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
|
||||
}
|
|
@ -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:
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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))
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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"))
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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 /"]
|
||||
}
|
||||
]
|
||||
}
|
|
@ -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"
|
|
@ -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
|
|
@ -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"
|
||||
}
|
||||
]
|
||||
}
|
|
@ -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())
|
||||
}
|
|
@ -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"
|
||||
}
|
||||
]
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
out/
|
|
@ -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
|
|
@ -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
|
|
@ -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"
|
|
@ -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"
|
|
@ -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
|
|
@ -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 /"]
|
||||
}
|
||||
]
|
||||
}
|
|
@ -0,0 +1,2 @@
|
|||
*.cmd text eol=crlf
|
||||
*.ps1 text eol=crlf
|
|
@ -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>
|
|
@ -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
|
|
@ -0,0 +1,2 @@
|
|||
@rem Silent mode, basic UI, no reboot
|
||||
e:\setup64 /s /v "/qb REBOOT=R"
|
|
@ -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:\\"]
|
||||
}
|
||||
]
|
||||
}
|
|
@ -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
|
||||
|
|
@ -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=
|
|
@ -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
|
|
@ -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
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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"))
|
||||
}
|
|
@ -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) {}
|
|
@ -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
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -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")
|
||||
}
|
|
@ -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())
|
||||
}
|
||||
}
|
|
@ -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) {}
|
|
@ -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) {}
|
|
@ -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) {}
|
|
@ -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'"
|
|
@ -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.
|
@ -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-----
|
|
@ -0,0 +1 @@
|
|||
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDYn3DdxuowlINMJS0VbepEeGqBdtI5bfZqeoRZBi9bjxYpcmZma65JWILS1wAwKdfym7lgaUAwKLlEEO12gT2ZnNQJ8ThDU6bjZLaMpGtJJuFT1OzyNNEvRXzVONrrhIS3mXV0MdLmyA0nLwLidZpahvUdP5iWg8SWWPzSiDChCgve5+mvmZ79M6sDcCkdd7BabR94GSSS43o2QvVn2wgsKy0uuMP5FUQ68j/CsdkRoulgng8uBLwuZM0poVp+arCBwLhVwDiRVuHHD1ffzm/6ZAuqmI4lr69LnV1Paog0TD7zRj4cvuTs4hlMuDzTfC5sjetRlD/Y9f9JMGrXM71H
|
Loading…
Reference in New Issue