Initial plugin for building XenServer images using Packer.IO
Signed-off-by: Rob Dobson <rob.dobson@citrix.com>
This commit is contained in:
parent
56820d268c
commit
4d634aaf70
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
*.swp
|
||||
*~
|
373
LICENSE
Normal file
373
LICENSE
Normal file
@ -0,0 +1,373 @@
|
||||
Mozilla Public License Version 2.0
|
||||
==================================
|
||||
|
||||
1. Definitions
|
||||
--------------
|
||||
|
||||
1.1. "Contributor"
|
||||
means each individual or legal entity that creates, contributes to
|
||||
the creation of, or owns Covered Software.
|
||||
|
||||
1.2. "Contributor Version"
|
||||
means the combination of the Contributions of others (if any) used
|
||||
by a Contributor and that particular Contributor's Contribution.
|
||||
|
||||
1.3. "Contribution"
|
||||
means Covered Software of a particular Contributor.
|
||||
|
||||
1.4. "Covered Software"
|
||||
means Source Code Form to which the initial Contributor has attached
|
||||
the notice in Exhibit A, the Executable Form of such Source Code
|
||||
Form, and Modifications of such Source Code Form, in each case
|
||||
including portions thereof.
|
||||
|
||||
1.5. "Incompatible With Secondary Licenses"
|
||||
means
|
||||
|
||||
(a) that the initial Contributor has attached the notice described
|
||||
in Exhibit B to the Covered Software; or
|
||||
|
||||
(b) that the Covered Software was made available under the terms of
|
||||
version 1.1 or earlier of the License, but not also under the
|
||||
terms of a Secondary License.
|
||||
|
||||
1.6. "Executable Form"
|
||||
means any form of the work other than Source Code Form.
|
||||
|
||||
1.7. "Larger Work"
|
||||
means a work that combines Covered Software with other material, in
|
||||
a separate file or files, that is not Covered Software.
|
||||
|
||||
1.8. "License"
|
||||
means this document.
|
||||
|
||||
1.9. "Licensable"
|
||||
means having the right to grant, to the maximum extent possible,
|
||||
whether at the time of the initial grant or subsequently, any and
|
||||
all of the rights conveyed by this License.
|
||||
|
||||
1.10. "Modifications"
|
||||
means any of the following:
|
||||
|
||||
(a) any file in Source Code Form that results from an addition to,
|
||||
deletion from, or modification of the contents of Covered
|
||||
Software; or
|
||||
|
||||
(b) any new file in Source Code Form that contains any Covered
|
||||
Software.
|
||||
|
||||
1.11. "Patent Claims" of a Contributor
|
||||
means any patent claim(s), including without limitation, method,
|
||||
process, and apparatus claims, in any patent Licensable by such
|
||||
Contributor that would be infringed, but for the grant of the
|
||||
License, by the making, using, selling, offering for sale, having
|
||||
made, import, or transfer of either its Contributions or its
|
||||
Contributor Version.
|
||||
|
||||
1.12. "Secondary License"
|
||||
means either the GNU General Public License, Version 2.0, the GNU
|
||||
Lesser General Public License, Version 2.1, the GNU Affero General
|
||||
Public License, Version 3.0, or any later versions of those
|
||||
licenses.
|
||||
|
||||
1.13. "Source Code Form"
|
||||
means the form of the work preferred for making modifications.
|
||||
|
||||
1.14. "You" (or "Your")
|
||||
means an individual or a legal entity exercising rights under this
|
||||
License. For legal entities, "You" includes any entity that
|
||||
controls, is controlled by, or is under common control with You. For
|
||||
purposes of this definition, "control" means (a) the power, direct
|
||||
or indirect, to cause the direction or management of such entity,
|
||||
whether by contract or otherwise, or (b) ownership of more than
|
||||
fifty percent (50%) of the outstanding shares or beneficial
|
||||
ownership of such entity.
|
||||
|
||||
2. License Grants and Conditions
|
||||
--------------------------------
|
||||
|
||||
2.1. Grants
|
||||
|
||||
Each Contributor hereby grants You a world-wide, royalty-free,
|
||||
non-exclusive license:
|
||||
|
||||
(a) under intellectual property rights (other than patent or trademark)
|
||||
Licensable by such Contributor to use, reproduce, make available,
|
||||
modify, display, perform, distribute, and otherwise exploit its
|
||||
Contributions, either on an unmodified basis, with Modifications, or
|
||||
as part of a Larger Work; and
|
||||
|
||||
(b) under Patent Claims of such Contributor to make, use, sell, offer
|
||||
for sale, have made, import, and otherwise transfer either its
|
||||
Contributions or its Contributor Version.
|
||||
|
||||
2.2. Effective Date
|
||||
|
||||
The licenses granted in Section 2.1 with respect to any Contribution
|
||||
become effective for each Contribution on the date the Contributor first
|
||||
distributes such Contribution.
|
||||
|
||||
2.3. Limitations on Grant Scope
|
||||
|
||||
The licenses granted in this Section 2 are the only rights granted under
|
||||
this License. No additional rights or licenses will be implied from the
|
||||
distribution or licensing of Covered Software under this License.
|
||||
Notwithstanding Section 2.1(b) above, no patent license is granted by a
|
||||
Contributor:
|
||||
|
||||
(a) for any code that a Contributor has removed from Covered Software;
|
||||
or
|
||||
|
||||
(b) for infringements caused by: (i) Your and any other third party's
|
||||
modifications of Covered Software, or (ii) the combination of its
|
||||
Contributions with other software (except as part of its Contributor
|
||||
Version); or
|
||||
|
||||
(c) under Patent Claims infringed by Covered Software in the absence of
|
||||
its Contributions.
|
||||
|
||||
This License does not grant any rights in the trademarks, service marks,
|
||||
or logos of any Contributor (except as may be necessary to comply with
|
||||
the notice requirements in Section 3.4).
|
||||
|
||||
2.4. Subsequent Licenses
|
||||
|
||||
No Contributor makes additional grants as a result of Your choice to
|
||||
distribute the Covered Software under a subsequent version of this
|
||||
License (see Section 10.2) or under the terms of a Secondary License (if
|
||||
permitted under the terms of Section 3.3).
|
||||
|
||||
2.5. Representation
|
||||
|
||||
Each Contributor represents that the Contributor believes its
|
||||
Contributions are its original creation(s) or it has sufficient rights
|
||||
to grant the rights to its Contributions conveyed by this License.
|
||||
|
||||
2.6. Fair Use
|
||||
|
||||
This License is not intended to limit any rights You have under
|
||||
applicable copyright doctrines of fair use, fair dealing, or other
|
||||
equivalents.
|
||||
|
||||
2.7. Conditions
|
||||
|
||||
Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted
|
||||
in Section 2.1.
|
||||
|
||||
3. Responsibilities
|
||||
-------------------
|
||||
|
||||
3.1. Distribution of Source Form
|
||||
|
||||
All distribution of Covered Software in Source Code Form, including any
|
||||
Modifications that You create or to which You contribute, must be under
|
||||
the terms of this License. You must inform recipients that the Source
|
||||
Code Form of the Covered Software is governed by the terms of this
|
||||
License, and how they can obtain a copy of this License. You may not
|
||||
attempt to alter or restrict the recipients' rights in the Source Code
|
||||
Form.
|
||||
|
||||
3.2. Distribution of Executable Form
|
||||
|
||||
If You distribute Covered Software in Executable Form then:
|
||||
|
||||
(a) such Covered Software must also be made available in Source Code
|
||||
Form, as described in Section 3.1, and You must inform recipients of
|
||||
the Executable Form how they can obtain a copy of such Source Code
|
||||
Form by reasonable means in a timely manner, at a charge no more
|
||||
than the cost of distribution to the recipient; and
|
||||
|
||||
(b) You may distribute such Executable Form under the terms of this
|
||||
License, or sublicense it under different terms, provided that the
|
||||
license for the Executable Form does not attempt to limit or alter
|
||||
the recipients' rights in the Source Code Form under this License.
|
||||
|
||||
3.3. Distribution of a Larger Work
|
||||
|
||||
You may create and distribute a Larger Work under terms of Your choice,
|
||||
provided that You also comply with the requirements of this License for
|
||||
the Covered Software. If the Larger Work is a combination of Covered
|
||||
Software with a work governed by one or more Secondary Licenses, and the
|
||||
Covered Software is not Incompatible With Secondary Licenses, this
|
||||
License permits You to additionally distribute such Covered Software
|
||||
under the terms of such Secondary License(s), so that the recipient of
|
||||
the Larger Work may, at their option, further distribute the Covered
|
||||
Software under the terms of either this License or such Secondary
|
||||
License(s).
|
||||
|
||||
3.4. Notices
|
||||
|
||||
You may not remove or alter the substance of any license notices
|
||||
(including copyright notices, patent notices, disclaimers of warranty,
|
||||
or limitations of liability) contained within the Source Code Form of
|
||||
the Covered Software, except that You may alter any license notices to
|
||||
the extent required to remedy known factual inaccuracies.
|
||||
|
||||
3.5. Application of Additional Terms
|
||||
|
||||
You may choose to offer, and to charge a fee for, warranty, support,
|
||||
indemnity or liability obligations to one or more recipients of Covered
|
||||
Software. However, You may do so only on Your own behalf, and not on
|
||||
behalf of any Contributor. You must make it absolutely clear that any
|
||||
such warranty, support, indemnity, or liability obligation is offered by
|
||||
You alone, and You hereby agree to indemnify every Contributor for any
|
||||
liability incurred by such Contributor as a result of warranty, support,
|
||||
indemnity or liability terms You offer. You may include additional
|
||||
disclaimers of warranty and limitations of liability specific to any
|
||||
jurisdiction.
|
||||
|
||||
4. Inability to Comply Due to Statute or Regulation
|
||||
---------------------------------------------------
|
||||
|
||||
If it is impossible for You to comply with any of the terms of this
|
||||
License with respect to some or all of the Covered Software due to
|
||||
statute, judicial order, or regulation then You must: (a) comply with
|
||||
the terms of this License to the maximum extent possible; and (b)
|
||||
describe the limitations and the code they affect. Such description must
|
||||
be placed in a text file included with all distributions of the Covered
|
||||
Software under this License. Except to the extent prohibited by statute
|
||||
or regulation, such description must be sufficiently detailed for a
|
||||
recipient of ordinary skill to be able to understand it.
|
||||
|
||||
5. Termination
|
||||
--------------
|
||||
|
||||
5.1. The rights granted under this License will terminate automatically
|
||||
if You fail to comply with any of its terms. However, if You become
|
||||
compliant, then the rights granted under this License from a particular
|
||||
Contributor are reinstated (a) provisionally, unless and until such
|
||||
Contributor explicitly and finally terminates Your grants, and (b) on an
|
||||
ongoing basis, if such Contributor fails to notify You of the
|
||||
non-compliance by some reasonable means prior to 60 days after You have
|
||||
come back into compliance. Moreover, Your grants from a particular
|
||||
Contributor are reinstated on an ongoing basis if such Contributor
|
||||
notifies You of the non-compliance by some reasonable means, this is the
|
||||
first time You have received notice of non-compliance with this License
|
||||
from such Contributor, and You become compliant prior to 30 days after
|
||||
Your receipt of the notice.
|
||||
|
||||
5.2. If You initiate litigation against any entity by asserting a patent
|
||||
infringement claim (excluding declaratory judgment actions,
|
||||
counter-claims, and cross-claims) alleging that a Contributor Version
|
||||
directly or indirectly infringes any patent, then the rights granted to
|
||||
You by any and all Contributors for the Covered Software under Section
|
||||
2.1 of this License shall terminate.
|
||||
|
||||
5.3. In the event of termination under Sections 5.1 or 5.2 above, all
|
||||
end user license agreements (excluding distributors and resellers) which
|
||||
have been validly granted by You or Your distributors under this License
|
||||
prior to termination shall survive termination.
|
||||
|
||||
************************************************************************
|
||||
* *
|
||||
* 6. Disclaimer of Warranty *
|
||||
* ------------------------- *
|
||||
* *
|
||||
* Covered Software is provided under this License on an "as is" *
|
||||
* basis, without warranty of any kind, either expressed, implied, or *
|
||||
* statutory, including, without limitation, warranties that the *
|
||||
* Covered Software is free of defects, merchantable, fit for a *
|
||||
* particular purpose or non-infringing. The entire risk as to the *
|
||||
* quality and performance of the Covered Software is with You. *
|
||||
* Should any Covered Software prove defective in any respect, You *
|
||||
* (not any Contributor) assume the cost of any necessary servicing, *
|
||||
* repair, or correction. This disclaimer of warranty constitutes an *
|
||||
* essential part of this License. No use of any Covered Software is *
|
||||
* authorized under this License except under this disclaimer. *
|
||||
* *
|
||||
************************************************************************
|
||||
|
||||
************************************************************************
|
||||
* *
|
||||
* 7. Limitation of Liability *
|
||||
* -------------------------- *
|
||||
* *
|
||||
* Under no circumstances and under no legal theory, whether tort *
|
||||
* (including negligence), contract, or otherwise, shall any *
|
||||
* Contributor, or anyone who distributes Covered Software as *
|
||||
* permitted above, be liable to You for any direct, indirect, *
|
||||
* special, incidental, or consequential damages of any character *
|
||||
* including, without limitation, damages for lost profits, loss of *
|
||||
* goodwill, work stoppage, computer failure or malfunction, or any *
|
||||
* and all other commercial damages or losses, even if such party *
|
||||
* shall have been informed of the possibility of such damages. This *
|
||||
* limitation of liability shall not apply to liability for death or *
|
||||
* personal injury resulting from such party's negligence to the *
|
||||
* extent applicable law prohibits such limitation. Some *
|
||||
* jurisdictions do not allow the exclusion or limitation of *
|
||||
* incidental or consequential damages, so this exclusion and *
|
||||
* limitation may not apply to You. *
|
||||
* *
|
||||
************************************************************************
|
||||
|
||||
8. Litigation
|
||||
-------------
|
||||
|
||||
Any litigation relating to this License may be brought only in the
|
||||
courts of a jurisdiction where the defendant maintains its principal
|
||||
place of business and such litigation shall be governed by laws of that
|
||||
jurisdiction, without reference to its conflict-of-law provisions.
|
||||
Nothing in this Section shall prevent a party's ability to bring
|
||||
cross-claims or counter-claims.
|
||||
|
||||
9. Miscellaneous
|
||||
----------------
|
||||
|
||||
This License represents the complete agreement concerning the subject
|
||||
matter hereof. If any provision of this License is held to be
|
||||
unenforceable, such provision shall be reformed only to the extent
|
||||
necessary to make it enforceable. Any law or regulation which provides
|
||||
that the language of a contract shall be construed against the drafter
|
||||
shall not be used to construe this License against a Contributor.
|
||||
|
||||
10. Versions of the License
|
||||
---------------------------
|
||||
|
||||
10.1. New Versions
|
||||
|
||||
Mozilla Foundation is the license steward. Except as provided in Section
|
||||
10.3, no one other than the license steward has the right to modify or
|
||||
publish new versions of this License. Each version will be given a
|
||||
distinguishing version number.
|
||||
|
||||
10.2. Effect of New Versions
|
||||
|
||||
You may distribute the Covered Software under the terms of the version
|
||||
of the License under which You originally received the Covered Software,
|
||||
or under the terms of any subsequent version published by the license
|
||||
steward.
|
||||
|
||||
10.3. Modified Versions
|
||||
|
||||
If you create software not governed by this License, and you want to
|
||||
create a new license for such software, you may create and use a
|
||||
modified version of this License if you rename the license and remove
|
||||
any references to the name of the license steward (except to note that
|
||||
such modified license differs from this License).
|
||||
|
||||
10.4. Distributing Source Code Form that is Incompatible With Secondary
|
||||
Licenses
|
||||
|
||||
If You choose to distribute Source Code Form that is Incompatible With
|
||||
Secondary Licenses under the terms of this version of the License, the
|
||||
notice described in Exhibit B of this License must be attached.
|
||||
|
||||
Exhibit A - Source Code Form License Notice
|
||||
-------------------------------------------
|
||||
|
||||
This Source Code Form is subject to the terms of the Mozilla Public
|
||||
License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
If it is not possible or desirable to put the notice in a particular
|
||||
file, then You may include the notice in a location (such as a LICENSE
|
||||
file in a relevant directory) where a recipient would be likely to look
|
||||
for such a notice.
|
||||
|
||||
You may add additional accurate notices of copyright ownership.
|
||||
|
||||
Exhibit B - "Incompatible With Secondary Licenses" Notice
|
||||
---------------------------------------------------------
|
||||
|
||||
This Source Code Form is "Incompatible With Secondary Licenses", as
|
||||
defined by the Mozilla Public License, v. 2.0.
|
1
build.sh
Executable file
1
build.sh
Executable file
@ -0,0 +1 @@
|
||||
go build -o $GOPATH/packer-builder-xenserver main.go
|
57
builder/xenserver/artifact.go
Normal file
57
builder/xenserver/artifact.go
Normal file
@ -0,0 +1,57 @@
|
||||
package xenserver
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"github.com/mitchellh/packer/packer"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
|
||||
type LocalArtifact struct {
|
||||
dir string
|
||||
f []string
|
||||
}
|
||||
|
||||
|
||||
func NewArtifact(dir string) (packer.Artifact, error) {
|
||||
files := make([]string, 0, 1)
|
||||
visit := func(path string, info os.FileInfo, err error) error {
|
||||
if !info.IsDir() {
|
||||
files = append(files, path)
|
||||
}
|
||||
return err
|
||||
}
|
||||
if err := filepath.Walk(dir, visit); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &LocalArtifact{
|
||||
dir: dir,
|
||||
f: files,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (*LocalArtifact) BuilderId() string {
|
||||
return BuilderId
|
||||
}
|
||||
|
||||
func (a *LocalArtifact) Files() []string {
|
||||
return a.f
|
||||
}
|
||||
|
||||
func (*LocalArtifact) Id() string {
|
||||
return "VM"
|
||||
}
|
||||
|
||||
func (a *LocalArtifact) String() string {
|
||||
return fmt.Sprintf("VM files in directory: %s", a.dir)
|
||||
}
|
||||
|
||||
func (a *LocalArtifact) State(name string) interface{} {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *LocalArtifact) Destroy() error {
|
||||
return os.RemoveAll(a.dir)
|
||||
}
|
@ -7,6 +7,10 @@ import (
|
||||
"fmt"
|
||||
"log"
|
||||
"errors"
|
||||
"time"
|
||||
"strings"
|
||||
"os"
|
||||
commonssh "github.com/mitchellh/packer/common/ssh"
|
||||
)
|
||||
|
||||
|
||||
@ -17,12 +21,46 @@ const BuilderId = "packer.xenserver"
|
||||
type config struct {
|
||||
common.PackerConfig `mapstructure:",squash"`
|
||||
|
||||
Username string `mapstructure:"username"`
|
||||
Password string `mapstructure:"password"`
|
||||
HostIp string `mapstructure:"host_ip"`
|
||||
IsoUrl string `mapstructure:"iso_url"`
|
||||
Username string `mapstructure:"username"`
|
||||
Password string `mapstructure:"password"`
|
||||
HostIp string `mapstructure:"host_ip"`
|
||||
IsoUrl string `mapstructure:"iso_url"`
|
||||
|
||||
InstanceName string `mapstructure:"instance_name"`
|
||||
InstanceName string `mapstructure:"instance_name"`
|
||||
RootDiskSize string `mapstructure:"root_disk_size"`
|
||||
CloneTemplate string `mapstructure:"clone_template"`
|
||||
IsoUuid string `mapstructure:"iso_uuid"`
|
||||
SrUuid string `mapstructure:"sr_uuid"`
|
||||
NetworkUuid string `mapstructure:"network_uuid"`
|
||||
|
||||
VncPortMin uint `mapstructure:"vnc_port_min"`
|
||||
VncPortMax uint `mapstructure:"vnc_port_max"`
|
||||
|
||||
BootCommand []string `mapstructure:"boot_command"`
|
||||
RawBootWait string `mapstructure:"boot_wait"`
|
||||
|
||||
BootWait time.Duration ``
|
||||
sshWaitTimeout time.Duration ``
|
||||
|
||||
ISOChecksum string `mapstructure:"iso_checksum"`
|
||||
ISOChecksumType string `mapstructure:"iso_checksum_type"`
|
||||
ISOUrls []string `mapstructure:"iso_urls"`
|
||||
ISOUrl string `mapstructure:"iso_url"`
|
||||
|
||||
HTTPDir string `mapstructure:"http_directory"`
|
||||
HTTPPortMin uint `mapstructure:"http_port_min"`
|
||||
HTTPPortMax uint `mapstructure:"http_port_max"`
|
||||
|
||||
LocalIp string `mapstructure:"local_ip"`
|
||||
PlatformArgs map[string]string `mapstructure:"platform_args"`
|
||||
|
||||
RawSSHWaitTimeout string `mapstructure:"ssh_wait_timeout"`
|
||||
|
||||
SSHPassword string `mapstructure:"ssh_password"`
|
||||
SSHUser string `mapstructure:"ssh_username"`
|
||||
SSHKeyPath string `mapstructure:"ssh_key_path"`
|
||||
|
||||
OutputDir string `mapstructure:"output_directory"`
|
||||
|
||||
tpl *packer.ConfigTemplate
|
||||
}
|
||||
@ -34,33 +72,75 @@ type Builder struct {
|
||||
}
|
||||
|
||||
|
||||
func (self *Builder) Prepare (raws ...interface{}) ([]string, error) {
|
||||
func (self *Builder) Prepare (raws ...interface{}) (params []string, retErr error) {
|
||||
|
||||
md, err := common.DecodeConfig(&self.config, raws...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
errs := common.CheckUnusedConfig(md)
|
||||
if errs == nil {
|
||||
errs = &packer.MultiError{}
|
||||
}
|
||||
|
||||
self.config.tpl, err = packer.NewConfigTemplate()
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
self.config.tpl.UserVars = self.config.PackerUserVars
|
||||
|
||||
|
||||
errs := common.CheckUnusedConfig(md)
|
||||
|
||||
|
||||
// Set default vaules
|
||||
|
||||
if self.config.VncPortMin == 0 {
|
||||
self.config.VncPortMin = 5900
|
||||
}
|
||||
|
||||
if self.config.VncPortMax == 0 {
|
||||
self.config.VncPortMax = 6000
|
||||
}
|
||||
|
||||
if self.config.RawBootWait == "" {
|
||||
self.config.RawBootWait = "5s"
|
||||
}
|
||||
|
||||
if self.config.HTTPPortMin == 0 {
|
||||
self.config.HTTPPortMin = 8000
|
||||
}
|
||||
|
||||
if self.config.HTTPPortMax == 0 {
|
||||
self.config.HTTPPortMax = 9000
|
||||
}
|
||||
|
||||
if self.config.RawSSHWaitTimeout == "" {
|
||||
self.config.RawSSHWaitTimeout = "200m"
|
||||
}
|
||||
|
||||
if self.config.OutputDir == "" {
|
||||
self.config.OutputDir = fmt.Sprintf("output-%s", self.config.PackerBuildName)
|
||||
}
|
||||
|
||||
templates := map[string]*string {
|
||||
"username": &self.config.Username,
|
||||
"password": &self.config.Password,
|
||||
"host_ip": &self.config.HostIp,
|
||||
"iso_url": &self.config.IsoUrl,
|
||||
"instance_name": &self.config.InstanceName,
|
||||
"username": &self.config.Username,
|
||||
"password": &self.config.Password,
|
||||
"host_ip": &self.config.HostIp,
|
||||
"iso_url": &self.config.IsoUrl,
|
||||
"instance_name": &self.config.InstanceName,
|
||||
"root_disk_size": &self.config.RootDiskSize,
|
||||
"clone_template": &self.config.CloneTemplate,
|
||||
"iso_uuid": &self.config.IsoUuid,
|
||||
"sr_uuid": &self.config.SrUuid,
|
||||
"network_uuid": &self.config.NetworkUuid,
|
||||
"boot_wait": &self.config.RawBootWait,
|
||||
"iso_checksum": &self.config.ISOChecksum,
|
||||
"iso_checksum_type": &self.config.ISOChecksumType,
|
||||
"http_directory": &self.config.HTTPDir,
|
||||
"local_ip": &self.config.LocalIp,
|
||||
"ssh_wait_timeout": &self.config.RawSSHWaitTimeout,
|
||||
"ssh_username": &self.config.SSHUser,
|
||||
"ssh_password": &self.config.SSHPassword,
|
||||
"ssh_key_path": &self.config.SSHKeyPath,
|
||||
"output_directory": &self.config.OutputDir,
|
||||
}
|
||||
|
||||
|
||||
@ -72,17 +152,220 @@ func (self *Builder) Prepare (raws ...interface{}) ([]string, error) {
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
if self.config.IsoUrl == "" {
|
||||
errs = packer.MultiErrorAppend(
|
||||
errs, errors.New("a iso url must be specified"))
|
||||
}
|
||||
return nil, nil
|
||||
*/
|
||||
|
||||
self.config.BootWait, err = time.ParseDuration(self.config.RawBootWait)
|
||||
if err != nil {
|
||||
errs = packer.MultiErrorAppend(
|
||||
errs, errors.New("Failed to parse boot_wait."))
|
||||
}
|
||||
|
||||
self.config.sshWaitTimeout, err = time.ParseDuration(self.config.RawSSHWaitTimeout)
|
||||
if err != nil {
|
||||
errs = packer.MultiErrorAppend(
|
||||
errs, fmt.Errorf("Failed to parse ssh_wait_timeout: %s", err))
|
||||
}
|
||||
|
||||
for i, command := range self.config.BootCommand {
|
||||
if err := self.config.tpl.Validate(command); err != nil {
|
||||
errs = packer.MultiErrorAppend(errs,
|
||||
fmt.Errorf("Error processing boot_command[%d]: %s", i, err))
|
||||
}
|
||||
}
|
||||
|
||||
if self.config.SSHUser == "" {
|
||||
errs = packer.MultiErrorAppend(
|
||||
errs, errors.New("An ssh_username must be specified."))
|
||||
}
|
||||
|
||||
if self.config.SSHKeyPath != "" {
|
||||
if _, err := os.Stat(self.config.SSHKeyPath); err != nil {
|
||||
errs = packer.MultiErrorAppend(
|
||||
errs, fmt.Errorf("ssh_key_path is invalid: %s", err))
|
||||
} else if _, err := commonssh.FileSigner(self.config.SSHKeyPath); err != nil {
|
||||
errs = packer.MultiErrorAppend(
|
||||
errs, fmt.Errorf("ssh_key_path is invalid: %s", err))
|
||||
}
|
||||
}
|
||||
|
||||
if self.config.Username == "" {
|
||||
errs = packer.MultiErrorAppend(
|
||||
errs, errors.New("A username for the xenserver host must be specified."))
|
||||
}
|
||||
|
||||
if self.config.Password == "" {
|
||||
errs = packer.MultiErrorAppend(
|
||||
errs, errors.New("A password for the xenserver host must be specified."))
|
||||
}
|
||||
|
||||
if self.config.HostIp == "" {
|
||||
errs = packer.MultiErrorAppend(
|
||||
errs, errors.New("An ip for the xenserver host must be specified."))
|
||||
}
|
||||
|
||||
if self.config.InstanceName == "" {
|
||||
errs = packer.MultiErrorAppend(
|
||||
errs, errors.New("An insatnce name must be specified."))
|
||||
}
|
||||
|
||||
if self.config.RootDiskSize == "" {
|
||||
errs = packer.MultiErrorAppend(
|
||||
errs, errors.New("A root disk size must be specified."))
|
||||
}
|
||||
|
||||
if self.config.CloneTemplate == "" {
|
||||
errs = packer.MultiErrorAppend(
|
||||
errs, errors.New("A template to clone from must be specified."))
|
||||
}
|
||||
|
||||
if self.config.IsoUuid == "" {
|
||||
errs = packer.MultiErrorAppend(
|
||||
errs, errors.New("a uuid for the installation iso must be specified."))
|
||||
}
|
||||
|
||||
if self.config.SrUuid == "" {
|
||||
errs = packer.MultiErrorAppend(
|
||||
errs, errors.New("a uuid for the sr used for the instance must be specified."))
|
||||
}
|
||||
|
||||
if self.config.NetworkUuid == "" {
|
||||
errs = packer.MultiErrorAppend(
|
||||
errs, errors.New("a uuid for the network used for the instance must be specified."))
|
||||
}
|
||||
|
||||
if self.config.LocalIp == "" {
|
||||
errs = packer.MultiErrorAppend(
|
||||
errs, errors.New("A local IP visible to XenServer's mangement interface is required to serve files."))
|
||||
}
|
||||
|
||||
if len(self.config.PlatformArgs) == 0 {
|
||||
pargs := make(map[string]string)
|
||||
pargs["viridian"] = "false"
|
||||
pargs["nx"] = "true"
|
||||
pargs["pae"] = "true"
|
||||
pargs["apic"] = "true"
|
||||
pargs["timeoffset"] = "0"
|
||||
pargs["acpi"] = "1"
|
||||
self.config.PlatformArgs = pargs
|
||||
}
|
||||
|
||||
if self.config.HTTPPortMin > self.config.HTTPPortMax {
|
||||
errs = packer.MultiErrorAppend(
|
||||
errs, errors.New("the HTTP min port must be less than the max"))
|
||||
}
|
||||
|
||||
if self.config.VncPortMin > self.config.VncPortMax {
|
||||
errs = packer.MultiErrorAppend(
|
||||
errs, errors.New("the VNC min port must be less than the max"))
|
||||
}
|
||||
|
||||
if self.config.ISOChecksumType == "" {
|
||||
errs = packer.MultiErrorAppend(
|
||||
errs, errors.New("The iso_checksum_type must be specified."))
|
||||
} else {
|
||||
self.config.ISOChecksumType = strings.ToLower(self.config.ISOChecksumType)
|
||||
if self.config.ISOChecksumType != "none" {
|
||||
if self.config.ISOChecksum == "" {
|
||||
errs = packer.MultiErrorAppend(
|
||||
errs, errors.New("Due to the file size being large, an iso_checksum is required."))
|
||||
} else {
|
||||
self.config.ISOChecksum = strings.ToLower(self.config.ISOChecksum)
|
||||
}
|
||||
|
||||
if hash := common.HashForType(self.config.ISOChecksumType); hash == nil {
|
||||
errs = packer.MultiErrorAppend(
|
||||
errs, fmt.Errorf("Unsupported checksum type: %s", self.config.ISOChecksumType))
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
if self.config.ISOUrl == "" {
|
||||
errs = packer.MultiErrorAppend(
|
||||
errs, errors.New("A ISO URL must be specfied."))
|
||||
} else {
|
||||
self.config.ISOUrls = []string{self.config.ISOUrl}
|
||||
}
|
||||
|
||||
for i, url := range self.config.ISOUrls {
|
||||
self.config.ISOUrls[i], err = common.DownloadableURL(url)
|
||||
if err != nil {
|
||||
errs = packer.MultiErrorAppend(
|
||||
errs, fmt.Errorf("Failed to parse the iso_url (%d): %s", i, err))
|
||||
}
|
||||
}
|
||||
|
||||
if len(errs.Errors) > 0 {
|
||||
retErr = errors.New(errs.Error())
|
||||
}
|
||||
|
||||
return nil, retErr
|
||||
|
||||
}
|
||||
|
||||
func (self *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packer.Artifact, error) {
|
||||
log.Println("Run, not yet implemented!")
|
||||
return nil, nil
|
||||
//Setup XAPI client
|
||||
client := NewXenAPIClient(self.config.HostIp, self.config.Username, self.config.Password)
|
||||
|
||||
err := client.Login()
|
||||
if err != nil {
|
||||
return nil, err.(error)
|
||||
}
|
||||
ui.Say("XAPI client session established")
|
||||
|
||||
client.GetHosts()
|
||||
|
||||
//Share state between the other steps using a statebag
|
||||
state := new(multistep.BasicStateBag)
|
||||
state.Put("cache", cache)
|
||||
state.Put("client", client)
|
||||
state.Put("config", self.config)
|
||||
state.Put("hook", hook)
|
||||
state.Put("ui", ui)
|
||||
|
||||
|
||||
//Build the steps
|
||||
steps := []multistep.Step{
|
||||
&common.StepDownload{
|
||||
Checksum: self.config.ISOChecksum,
|
||||
ChecksumType: self.config.ISOChecksumType,
|
||||
Description: "ISO",
|
||||
ResultKey: "iso_path",
|
||||
Url: self.config.ISOUrls,
|
||||
},
|
||||
new(stepPrepareOutputDir),
|
||||
new(stepHTTPServer),
|
||||
new(stepUploadIso),
|
||||
new(stepCreateInstance),
|
||||
new(stepStartVmPaused),
|
||||
new(stepForwardVncPortOverSsh),
|
||||
new(stepBootWait),
|
||||
new(stepTypeBootCommand),
|
||||
new(stepWait),
|
||||
&common.StepConnectSSH{
|
||||
SSHAddress: sshAddress,
|
||||
SSHConfig: sshConfig,
|
||||
SSHWaitTimeout: self.config.sshWaitTimeout,
|
||||
},
|
||||
new(common.StepProvision),
|
||||
new(stepShutdownAndExport),
|
||||
}
|
||||
|
||||
self.runner = &multistep.BasicRunner{Steps: steps}
|
||||
self.runner.Run(state)
|
||||
|
||||
artifact, _ := NewArtifact(self.config.OutputDir)
|
||||
|
||||
if rawErr, ok := state.GetOk("error"); ok {
|
||||
return nil, rawErr.(error)
|
||||
}
|
||||
|
||||
return artifact, nil
|
||||
}
|
||||
|
||||
|
||||
|
530
builder/xenserver/client.go
Normal file
530
builder/xenserver/client.go
Normal file
@ -0,0 +1,530 @@
|
||||
package xenserver
|
||||
|
||||
import (
|
||||
"github.com/nilshell/xmlrpc"
|
||||
"log"
|
||||
"fmt"
|
||||
"errors"
|
||||
)
|
||||
|
||||
func check(err error) {
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
type XenAPIClient struct {
|
||||
Session interface{}
|
||||
Host string
|
||||
Url string
|
||||
Username string
|
||||
Password string
|
||||
RPC *xmlrpc.Client
|
||||
}
|
||||
|
||||
|
||||
type APIResult struct {
|
||||
Status string
|
||||
Value interface{}
|
||||
ErrorDescription string
|
||||
}
|
||||
|
||||
type VM struct {
|
||||
Ref string
|
||||
Client *XenAPIClient
|
||||
}
|
||||
|
||||
type SR struct {
|
||||
Ref string
|
||||
Client *XenAPIClient
|
||||
}
|
||||
|
||||
type VDI struct {
|
||||
Ref string
|
||||
Client *XenAPIClient
|
||||
}
|
||||
|
||||
type Network struct {
|
||||
Ref string
|
||||
Client *XenAPIClient
|
||||
}
|
||||
|
||||
type VBD struct {
|
||||
Ref string
|
||||
Client *XenAPIClient
|
||||
}
|
||||
|
||||
func (c *XenAPIClient) RPCCall (result interface{}, method string, params []interface{}) (err error) {
|
||||
fmt.Println(params)
|
||||
p := new(xmlrpc.Params)
|
||||
p.Params = params
|
||||
err = c.RPC.Call(method, *p, result)
|
||||
return err
|
||||
}
|
||||
|
||||
|
||||
func (client *XenAPIClient) Login () (err error) {
|
||||
//Do loging call
|
||||
result := xmlrpc.Struct{}
|
||||
|
||||
params := make([]interface{}, 2)
|
||||
params[0] = client.Username
|
||||
params[1] = client.Password
|
||||
|
||||
err = client.RPCCall(&result, "session.login_with_password", params)
|
||||
client.Session = result["Value"]
|
||||
return err
|
||||
}
|
||||
|
||||
func (client *XenAPIClient) APICall (result *APIResult, method string, params ...interface{}) (err error) {
|
||||
if client.Session == nil {
|
||||
fmt.Println("Error: no session")
|
||||
return fmt.Errorf("No session. Unable to make call")
|
||||
}
|
||||
|
||||
//Make a params slice which will include the session
|
||||
p := make([]interface{}, len(params) + 1)
|
||||
p[0] = client.Session
|
||||
|
||||
if params != nil {
|
||||
for idx, element := range params {
|
||||
p[idx+1] = element
|
||||
}
|
||||
}
|
||||
|
||||
res := xmlrpc.Struct{}
|
||||
|
||||
err = client.RPCCall(&res, method, p)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
result.Status = res["Status"].(string)
|
||||
|
||||
if result.Status != "Success" {
|
||||
fmt.Println("Encountered an API error: ", result.Status)
|
||||
fmt.Println(res["ErrorDescription"])
|
||||
return errors.New("API Error occurred")
|
||||
} else {
|
||||
result.Value = res["Value"]
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
func (client *XenAPIClient) GetHosts () (err error) {
|
||||
result := APIResult{}
|
||||
_ = client.APICall(&result, "host.get_all")
|
||||
hosts := result.Value
|
||||
fmt.Println(hosts)
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
func (client *XenAPIClient) GetVMByUuid (vm_uuid string) (vm *VM, err error) {
|
||||
vm = new(VM)
|
||||
result := APIResult{}
|
||||
err = client.APICall(&result, "VM.get_by_uuid", vm_uuid)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
vm.Ref = result.Value.(string)
|
||||
vm.Client = client
|
||||
return
|
||||
}
|
||||
|
||||
func (client *XenAPIClient) GetNetworkByUuid (network_uuid string) (network *Network, err error) {
|
||||
network = new(Network)
|
||||
result := APIResult{}
|
||||
err = client.APICall(&result, "network.get_by_uuid", network_uuid)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
network.Ref = result.Value.(string)
|
||||
network.Client = client
|
||||
return
|
||||
}
|
||||
|
||||
func (client *XenAPIClient) GetSRByUuid (sr_uuid string) (sr *SR, err error) {
|
||||
sr = new(SR)
|
||||
result := APIResult{}
|
||||
err = client.APICall(&result, "SR.get_by_uuid", sr_uuid)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
sr.Ref = result.Value.(string)
|
||||
sr.Client = client
|
||||
return
|
||||
}
|
||||
|
||||
func (client *XenAPIClient) GetVdiByUuid (vdi_uuid string) (vdi *VDI, err error) {
|
||||
vdi = new(VDI)
|
||||
result := APIResult{}
|
||||
err = client.APICall(&result, "VDI.get_by_uuid", vdi_uuid)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
vdi.Ref = result.Value.(string)
|
||||
vdi.Client = client
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
// VM associated functions
|
||||
|
||||
func (self *VM) Clone (label string) (new_instance *VM, err error) {
|
||||
new_instance = new(VM)
|
||||
|
||||
result := APIResult{}
|
||||
err = self.Client.APICall(&result, "VM.clone", self.Ref, label)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
new_instance.Ref = result.Value.(string)
|
||||
new_instance.Client = self.Client
|
||||
return
|
||||
}
|
||||
|
||||
func (self *VM) Start(paused, force bool) (err error) {
|
||||
result := APIResult{}
|
||||
err = self.Client.APICall(&result, "VM.start", self.Ref, paused, force)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (self *VM) CleanShutdown() (err error) {
|
||||
result := APIResult{}
|
||||
err = self.Client.APICall(&result, "VM.clean_shutdown", self.Ref)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (self *VM) Unpause () (err error) {
|
||||
result := APIResult{}
|
||||
err = self.Client.APICall(&result, "VM.unpause", self.Ref)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (self *VM) SetPVBootloader(pv_bootloader, pv_args string) (err error) {
|
||||
result := APIResult{}
|
||||
err = self.Client.APICall(&result, "VM.set_PV_bootloader", self.Ref, pv_bootloader)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
result = APIResult{}
|
||||
err = self.Client.APICall(&result, "VM.set_PV_bootloader_args", self.Ref, pv_args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (self *VM) GetDomainId() (domid string, err error) {
|
||||
result := APIResult{}
|
||||
err = self.Client.APICall(&result, "VM.get_domid", self.Ref)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
domid = result.Value.(string)
|
||||
return domid, nil
|
||||
}
|
||||
|
||||
func (self *VM) GetPowerState() (state string, err error) {
|
||||
result := APIResult{}
|
||||
err = self.Client.APICall(&result, "VM.get_power_state", self.Ref)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
state = result.Value.(string)
|
||||
return state, nil
|
||||
}
|
||||
|
||||
func (self *VM) GetUuid() (uuid string, err error) {
|
||||
result := APIResult{}
|
||||
err = self.Client.APICall(&result, "VM.get_uuid", self.Ref)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
uuid = result.Value.(string)
|
||||
return uuid, nil
|
||||
}
|
||||
|
||||
func (self *VM) GetVBDs() (vbds []VBD, err error) {
|
||||
vbds = make([]VBD, 0)
|
||||
result := APIResult{}
|
||||
err = self.Client.APICall(&result, "VM.get_VBDs", self.Ref)
|
||||
if err != nil {
|
||||
return vbds, err
|
||||
}
|
||||
for _, elem := range result.Value.([]interface{}) {
|
||||
vbd := VBD{}
|
||||
vbd.Ref = elem.(string)
|
||||
vbd.Client = self.Client
|
||||
vbds = append(vbds, vbd)
|
||||
}
|
||||
|
||||
return vbds, nil
|
||||
}
|
||||
|
||||
func (self *VM) GetDisks() (vdis []*VDI, err error) {
|
||||
// Return just data disks (non-isos)
|
||||
vdis = make([]*VDI, 0)
|
||||
vbds, err := self.GetVBDs()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, vbd := range vbds {
|
||||
rec, err := vbd.GetRecord()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if rec["type"] == "Disk" {
|
||||
|
||||
vdi, err := vbd.GetVDI()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
vdis = append(vdis, vdi)
|
||||
|
||||
}
|
||||
}
|
||||
return vdis, nil
|
||||
}
|
||||
|
||||
func (self *VM) GetGuestMetricsRef() (ref string, err error) {
|
||||
result := APIResult{}
|
||||
err = self.Client.APICall(&result, "VM.get_guest_metrics", self.Ref)
|
||||
if err != nil {
|
||||
return "", nil
|
||||
}
|
||||
ref = result.Value.(string)
|
||||
return ref, err
|
||||
}
|
||||
|
||||
func (self *VM) GetGuestMetrics() (metrics map[string]interface{}, err error) {
|
||||
metrics = make(map[string]interface{})
|
||||
metrics_ref, err := self.GetGuestMetricsRef()
|
||||
if err != nil {
|
||||
return metrics, err
|
||||
}
|
||||
|
||||
result := APIResult{}
|
||||
err = self.Client.APICall(&result, "VM_guest_metrics.get_record", metrics_ref)
|
||||
if err != nil {
|
||||
return metrics, nil
|
||||
}
|
||||
for k, v := range result.Value.(xmlrpc.Struct) {
|
||||
metrics[k] = v
|
||||
}
|
||||
return metrics, nil
|
||||
}
|
||||
|
||||
func (self *VM) SetStaticMemoryRange(min, max string) (err error) {
|
||||
result := APIResult{}
|
||||
err = self.Client.APICall(&result, "VM.set_memory_limits", self.Ref, min, max, min, max)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (self *VM) ConnectVdi (vdi *VDI, iso bool) (err error) {
|
||||
|
||||
// 1. Create a VBD
|
||||
|
||||
vbd_rec := make(xmlrpc.Struct)
|
||||
vbd_rec["VM"] = self.Ref
|
||||
vbd_rec["VDI"] = vdi.Ref
|
||||
vbd_rec["userdevice"] = "autodetect"
|
||||
vbd_rec["unpluggable"] = false
|
||||
vbd_rec["empty"] = false
|
||||
vbd_rec["other_config"] = make(xmlrpc.Struct)
|
||||
vbd_rec["qos_algorithm_type"] = ""
|
||||
vbd_rec["qos_algorithm_params"] = make(xmlrpc.Struct)
|
||||
|
||||
if iso {
|
||||
vbd_rec["mode"] = "RO"
|
||||
vbd_rec["bootable"] = true
|
||||
vbd_rec["type"] = "CD"
|
||||
} else {
|
||||
vbd_rec["mode"] = "RW"
|
||||
vbd_rec["bootable"] = false
|
||||
vbd_rec["type"] = "Disk"
|
||||
}
|
||||
|
||||
result := APIResult{}
|
||||
err = self.Client.APICall(&result, "VBD.create", vbd_rec)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
vbd_ref := result.Value.(string)
|
||||
fmt.Println("VBD Ref:", vbd_ref)
|
||||
|
||||
result = APIResult{}
|
||||
err = self.Client.APICall(&result, "VBD.get_uuid", vbd_ref)
|
||||
|
||||
fmt.Println("VBD UUID: ", result.Value.(string))
|
||||
/*
|
||||
// 2. Plug VBD (Non need - the VM hasn't booted.
|
||||
// @todo - check VM state
|
||||
result = APIResult{}
|
||||
err = self.Client.APICall(&result, "VBD.plug", vbd_ref)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*/
|
||||
return
|
||||
}
|
||||
|
||||
func (self *VM) SetPlatform(params map[string]string) (err error) {
|
||||
result := APIResult{}
|
||||
platform_rec := make(xmlrpc.Struct)
|
||||
for key, value := range params {
|
||||
platform_rec[key] = value
|
||||
}
|
||||
|
||||
err = self.Client.APICall(&result, "VM.set_platform", self.Ref, platform_rec)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
func (self *VM) ConnectNetwork (network *Network, device string) (err error) {
|
||||
// Create the VIF
|
||||
|
||||
vif_rec := make(xmlrpc.Struct)
|
||||
vif_rec["network"] = network.Ref
|
||||
vif_rec["VM"] = self.Ref
|
||||
vif_rec["MAC"] = ""
|
||||
vif_rec["device"] = device
|
||||
vif_rec["MTU"] = "1504"
|
||||
vif_rec["other_config"] = make(xmlrpc.Struct)
|
||||
vif_rec["qos_algorithm_type"] = ""
|
||||
vif_rec["qos_algorithm_params"] = make(xmlrpc.Struct)
|
||||
|
||||
result := APIResult{}
|
||||
err = self.Client.APICall(&result, "VIF.create", vif_rec)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Setters
|
||||
|
||||
func (self *VM) SetIsATemplate (is_a_template bool) (err error) {
|
||||
result := APIResult{}
|
||||
err = self.Client.APICall(&result, "VM.set_is_a_template", self.Ref, is_a_template)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// SR associated functions
|
||||
|
||||
func (self *SR) CreateVdi (name_label, size string) (vdi *VDI, err error) {
|
||||
vdi = new(VDI)
|
||||
|
||||
vdi_rec := make(xmlrpc.Struct)
|
||||
vdi_rec["name_label"] = name_label
|
||||
vdi_rec["SR"] = self.Ref
|
||||
vdi_rec["virtual_size"] = size
|
||||
vdi_rec["type"] = "user"
|
||||
vdi_rec["sharable"] = false
|
||||
vdi_rec["read_only"] = false
|
||||
|
||||
oc := make(xmlrpc.Struct)
|
||||
oc["temp"] = "temp"
|
||||
vdi_rec["other_config"] = oc
|
||||
|
||||
|
||||
result := APIResult{}
|
||||
err = self.Client.APICall(&result, "VDI.create", vdi_rec)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
vdi.Ref = result.Value.(string)
|
||||
vdi.Client = self.Client
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// VBD associated functions
|
||||
func (self *VBD) GetRecord () (record map[string]interface{}, err error) {
|
||||
record = make(map[string]interface{})
|
||||
result := APIResult{}
|
||||
err = self.Client.APICall(&result, "VBD.get_record", self.Ref)
|
||||
if err != nil {
|
||||
return record, err
|
||||
}
|
||||
for k, v := range result.Value.(xmlrpc.Struct) {
|
||||
record[k] = v
|
||||
}
|
||||
return record, nil
|
||||
}
|
||||
|
||||
func (self *VBD) GetVDI () (vdi *VDI, err error) {
|
||||
vbd_rec, err := self.GetRecord()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
vdi = new(VDI)
|
||||
vdi.Ref = vbd_rec["VDI"].(string)
|
||||
vdi.Client = self.Client
|
||||
|
||||
return vdi, nil
|
||||
}
|
||||
|
||||
func (self *VBD) Eject () (err error) {
|
||||
result := APIResult{}
|
||||
err = self.Client.APICall(&result, "VBD.eject", self.Ref)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// VDI associated functions
|
||||
|
||||
func (self *VDI) GetUuid () (vdi_uuid string, err error) {
|
||||
result := APIResult{}
|
||||
err = self.Client.APICall(&result, "VDI.get_uuid", self.Ref)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
vdi_uuid = result.Value.(string)
|
||||
return vdi_uuid, nil
|
||||
}
|
||||
|
||||
|
||||
// Client Initiator
|
||||
|
||||
func NewXenAPIClient (host, username, password string) (client XenAPIClient) {
|
||||
client.Host = host
|
||||
client.Url = "http://" + host
|
||||
client.Username = username
|
||||
client.Password = password
|
||||
client.RPC, _ = xmlrpc.NewClient(client.Url, nil)
|
||||
return
|
||||
}
|
39
builder/xenserver/ssh.go
Normal file
39
builder/xenserver/ssh.go
Normal file
@ -0,0 +1,39 @@
|
||||
package xenserver
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
gossh "code.google.com/p/go.crypto/ssh"
|
||||
"github.com/mitchellh/multistep"
|
||||
commonssh "github.com/mitchellh/packer/common/ssh"
|
||||
"github.com/mitchellh/packer/communicator/ssh"
|
||||
)
|
||||
|
||||
func sshAddress(state multistep.StateBag) (string, error) {
|
||||
sshIP := state.Get("ssh_address").(string)
|
||||
sshHostPort := 22
|
||||
return fmt.Sprintf("%s:%d", sshIP, sshHostPort), nil
|
||||
}
|
||||
|
||||
func sshConfig(state multistep.StateBag) (*gossh.ClientConfig, error) {
|
||||
config := state.Get("config").(config)
|
||||
|
||||
auth := []gossh.AuthMethod{
|
||||
gossh.Password(config.SSHPassword),
|
||||
gossh.KeyboardInteractive(
|
||||
ssh.PasswordKeyboardInteractive(config.SSHPassword)),
|
||||
}
|
||||
|
||||
if config.SSHKeyPath != "" {
|
||||
signer, err := commonssh.FileSigner(config.SSHKeyPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
auth = append(auth, gossh.PublicKeys(signer))
|
||||
}
|
||||
|
||||
return &gossh.ClientConfig{
|
||||
User: config.SSHUser,
|
||||
Auth: auth,
|
||||
}, nil
|
||||
}
|
30
builder/xenserver/step_boot_wait.go
Normal file
30
builder/xenserver/step_boot_wait.go
Normal file
@ -0,0 +1,30 @@
|
||||
package xenserver
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/mitchellh/multistep"
|
||||
"github.com/mitchellh/packer/packer"
|
||||
"time"
|
||||
)
|
||||
|
||||
|
||||
type stepBootWait struct{}
|
||||
|
||||
func (self *stepBootWait) Run(state multistep.StateBag) multistep.StepAction {
|
||||
client := state.Get("client").(XenAPIClient)
|
||||
config := state.Get("config").(config)
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
|
||||
instance, _ := client.GetVMByUuid(state.Get("instance_uuid").(string))
|
||||
ui.Say("Unpausing VM " + state.Get("instance_uuid").(string))
|
||||
instance.Unpause()
|
||||
|
||||
if int64(config.BootWait) > 0 {
|
||||
ui.Say(fmt.Sprintf("Waiting %s for boot...", config.BootWait))
|
||||
time.Sleep(config.BootWait)
|
||||
}
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
func (self *stepBootWait) Cleanup(state multistep.StateBag) {}
|
||||
|
80
builder/xenserver/step_create_instance.go
Normal file
80
builder/xenserver/step_create_instance.go
Normal file
@ -0,0 +1,80 @@
|
||||
package xenserver
|
||||
|
||||
|
||||
import (
|
||||
"github.com/mitchellh/multistep"
|
||||
"github.com/mitchellh/packer/packer"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
type stepCreateInstance struct {
|
||||
InstanceId string
|
||||
}
|
||||
|
||||
func (self *stepCreateInstance) Run(state multistep.StateBag) multistep.StepAction {
|
||||
|
||||
client := state.Get("client").(XenAPIClient)
|
||||
config := state.Get("config").(config)
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
|
||||
ui.Say("Step: Create Instance")
|
||||
|
||||
|
||||
// Get the template to clone from
|
||||
template, _ := client.GetVMByUuid(config.CloneTemplate)
|
||||
|
||||
// Clone that VM template
|
||||
instance, _ := template.Clone(config.InstanceName)
|
||||
instance.SetIsATemplate(false)
|
||||
instance.SetStaticMemoryRange("1024000000", "1024000000")
|
||||
instance.SetPlatform(config.PlatformArgs)
|
||||
|
||||
// Create VDI for the instance
|
||||
sr, _ := client.GetSRByUuid(config.SrUuid)
|
||||
vdi, _ := sr.CreateVdi("Packer-disk", config.RootDiskSize)
|
||||
|
||||
instance.ConnectVdi(vdi, false)
|
||||
|
||||
// Connect Network
|
||||
network, err := client.GetNetworkByUuid(config.NetworkUuid)
|
||||
if err != nil {
|
||||
ui.Say(err.Error())
|
||||
}
|
||||
err = instance.ConnectNetwork(network, "0")
|
||||
|
||||
if err != nil {
|
||||
ui.Say(err.Error())
|
||||
}
|
||||
|
||||
// Connect the ISO
|
||||
//iso_vdi_uuid := state.Get("iso_vdi_uuid").(string)
|
||||
|
||||
iso, _ := client.GetVdiByUuid(config.IsoUuid)
|
||||
//ui.Say("Using VDI: " + iso_vdi_uuid)
|
||||
//iso, _ := client.GetVdiByUuid(iso_vdi_uuid)
|
||||
instance.ConnectVdi(iso, true)
|
||||
|
||||
// Stash the VM reference
|
||||
self.InstanceId, _ = instance.GetUuid()
|
||||
state.Put("instance_uuid", self.InstanceId)
|
||||
ui.Say(fmt.Sprintf("Created instance '%s'", self.InstanceId))
|
||||
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
|
||||
func (self *stepCreateInstance) Cleanup(state multistep.StateBag) {
|
||||
|
||||
// client := state.Get("client").(*XenAPIClient)
|
||||
// config := state.Get("config").(config)
|
||||
// ui := state.Get("ui").(packer.Ui)
|
||||
|
||||
// If instance hasn't been created, we have nothing to do.
|
||||
if self.InstanceId == "" {
|
||||
return
|
||||
}
|
||||
|
||||
// @todo: destroy the created instance.
|
||||
|
||||
return
|
||||
}
|
143
builder/xenserver/step_forward_vnc_port_over_ssh.go
Normal file
143
builder/xenserver/step_forward_vnc_port_over_ssh.go
Normal file
@ -0,0 +1,143 @@
|
||||
package xenserver
|
||||
|
||||
|
||||
import (
|
||||
"github.com/mitchellh/multistep"
|
||||
"github.com/mitchellh/packer/packer"
|
||||
"fmt"
|
||||
"code.google.com/p/go.crypto/ssh"
|
||||
"bytes"
|
||||
"net"
|
||||
"io"
|
||||
"strconv"
|
||||
"log"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type stepForwardVncPortOverSsh struct {}
|
||||
|
||||
|
||||
func execute_ssh_cmd (cmd, host, port, username, password string) (stdout string, err error) {
|
||||
// Setup connection config
|
||||
config := &ssh.ClientConfig {
|
||||
User: username,
|
||||
Auth: []ssh.AuthMethod {
|
||||
ssh.Password(password),
|
||||
},
|
||||
}
|
||||
|
||||
client, err := ssh.Dial("tcp", host + ":" + port, config)
|
||||
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
//Create session
|
||||
session, err := client.NewSession()
|
||||
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
defer session.Close()
|
||||
|
||||
var b bytes.Buffer
|
||||
session.Stdout = &b
|
||||
if err := session.Run(cmd); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
session.Close()
|
||||
return strings.Trim(b.String(), "\n"), nil
|
||||
}
|
||||
|
||||
func forward(local_conn net.Conn, config *ssh.ClientConfig, server, remote_port string) {
|
||||
ssh_client_conn, err := ssh.Dial("tcp", server + ":22", config)
|
||||
if err != nil {
|
||||
log.Fatalf("local ssh.Dial error: %s", err)
|
||||
}
|
||||
|
||||
ssh_conn, err := ssh_client_conn.Dial("tcp", "127.0.0.1:" + remote_port)
|
||||
if err != nil {
|
||||
log.Fatalf("ssh.Dial error: %s", err)
|
||||
}
|
||||
|
||||
go func() {
|
||||
_, err = io.Copy(ssh_conn, local_conn)
|
||||
if err != nil {
|
||||
log.Fatalf("io.copy failed: %v", err)
|
||||
}
|
||||
}()
|
||||
|
||||
go func() {
|
||||
_, err = io.Copy(local_conn, ssh_conn)
|
||||
if err != nil {
|
||||
log.Fatalf("io.copy failed: %v", err)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
func ssh_port_forward(local_port, remote_port, host, username, password string) (err error) {
|
||||
|
||||
config := &ssh.ClientConfig {
|
||||
User: username,
|
||||
Auth: []ssh.AuthMethod{
|
||||
ssh.Password(password),
|
||||
},
|
||||
}
|
||||
|
||||
// Listen on a local port
|
||||
local_listener, err := net.Listen("tcp", "127.0.0.1:" + local_port)
|
||||
if err != nil {
|
||||
log.Fatalf("Local listen failed: %s", err)
|
||||
return err
|
||||
}
|
||||
|
||||
for {
|
||||
local_connection, err := local_listener.Accept()
|
||||
|
||||
if err != nil {
|
||||
log.Fatalf("Local accept failed: %s", err)
|
||||
return err
|
||||
}
|
||||
|
||||
|
||||
// Forward to a remote port
|
||||
go forward(local_connection, config, host, remote_port)
|
||||
}
|
||||
|
||||
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *stepForwardVncPortOverSsh) Run(state multistep.StateBag) multistep.StepAction {
|
||||
|
||||
// client := state.Get("client").(XenAPIClient)
|
||||
|
||||
config := state.Get("config").(config)
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
|
||||
ui.Say("Step: forward the instances VNC port over SSH")
|
||||
|
||||
domid := state.Get("domid").(string)
|
||||
cmd := fmt.Sprintf("xenstore-read /local/domain/%s/console/vnc-port", domid)
|
||||
|
||||
remote_vncport, _ := execute_ssh_cmd(cmd, config.HostIp, "22", config.Username, config.Password)
|
||||
|
||||
ui.Say("The VNC port is " + remote_vncport)
|
||||
// Just take the min port for the moment
|
||||
state.Put("local_vnc_port", config.VncPortMin)
|
||||
local_port := strconv.Itoa(int(config.VncPortMin))
|
||||
ui.Say("About to setup SSH Port forward setup on local port " + local_port)
|
||||
|
||||
|
||||
go ssh_port_forward(local_port, remote_vncport, config.HostIp, config.Username, config.Password)
|
||||
ui.Say("Port forward setup.")
|
||||
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
|
||||
func (self *stepForwardVncPortOverSsh) Cleanup(state multistep.StateBag) {
|
||||
}
|
78
builder/xenserver/step_http_server.go
Normal file
78
builder/xenserver/step_http_server.go
Normal file
@ -0,0 +1,78 @@
|
||||
package xenserver
|
||||
|
||||
// Taken from mitchellh/packer/builder/qemu/step_http_server.go
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/mitchellh/multistep"
|
||||
"github.com/mitchellh/packer/packer"
|
||||
"log"
|
||||
"math/rand"
|
||||
"net"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// This step creates and runs the HTTP server that is serving files from the
|
||||
// directory specified by the 'http_directory` configuration parameter in the
|
||||
// template.
|
||||
//
|
||||
// Uses:
|
||||
// config *config
|
||||
// ui packer.Ui
|
||||
//
|
||||
// Produces:
|
||||
// http_port int - The port the HTTP server started on.
|
||||
type stepHTTPServer struct {
|
||||
l net.Listener
|
||||
}
|
||||
|
||||
func (s *stepHTTPServer) Run(state multistep.StateBag) multistep.StepAction {
|
||||
config := state.Get("config").(config)
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
|
||||
var httpPort uint = 0
|
||||
if config.HTTPDir == "" {
|
||||
state.Put("http_port", httpPort)
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
// Find an available TCP port for our HTTP server
|
||||
var httpAddr string
|
||||
portRange := int(config.HTTPPortMax - config.HTTPPortMin)
|
||||
for {
|
||||
var err error
|
||||
var offset uint = 0
|
||||
|
||||
if portRange > 0 {
|
||||
// Intn will panic if portRange == 0, so we do a check.
|
||||
offset = uint(rand.Intn(portRange))
|
||||
}
|
||||
|
||||
httpPort = offset + config.HTTPPortMin
|
||||
httpAddr = fmt.Sprintf(":%d", httpPort)
|
||||
log.Printf("Trying port: %d", httpPort)
|
||||
s.l, err = net.Listen("tcp", httpAddr)
|
||||
if err == nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
ui.Say(fmt.Sprintf("Starting HTTP server on port %d", httpPort))
|
||||
|
||||
// Start the HTTP server and run it in the background
|
||||
fileServer := http.FileServer(http.Dir(config.HTTPDir))
|
||||
server := &http.Server{Addr: httpAddr, Handler: fileServer}
|
||||
go server.Serve(s.l)
|
||||
|
||||
// Save the address into the state so it can be accessed in the future
|
||||
state.Put("http_port", httpPort)
|
||||
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
func (s *stepHTTPServer) Cleanup(multistep.StateBag) {
|
||||
if s.l != nil {
|
||||
// Close the listener so that the HTTP server stops
|
||||
s.l.Close()
|
||||
}
|
||||
}
|
51
builder/xenserver/step_prepare_output_dir.go
Normal file
51
builder/xenserver/step_prepare_output_dir.go
Normal file
@ -0,0 +1,51 @@
|
||||
package xenserver
|
||||
|
||||
/* Taken from https://raw.githubusercontent.com/mitchellh/packer/master/builder/qemu/step_prepare_output_dir.go */
|
||||
|
||||
import (
|
||||
"github.com/mitchellh/multistep"
|
||||
"github.com/mitchellh/packer/packer"
|
||||
"log"
|
||||
"os"
|
||||
"time"
|
||||
)
|
||||
|
||||
type stepPrepareOutputDir struct{}
|
||||
|
||||
func (stepPrepareOutputDir) Run(state multistep.StateBag) multistep.StepAction {
|
||||
config := state.Get("config").(config)
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
|
||||
if _, err := os.Stat(config.OutputDir); err == nil && config.PackerForce {
|
||||
ui.Say("Deleting previous output directory...")
|
||||
os.RemoveAll(config.OutputDir)
|
||||
}
|
||||
|
||||
if err := os.MkdirAll(config.OutputDir, 0755); err != nil {
|
||||
state.Put("error", err)
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
func (stepPrepareOutputDir) Cleanup(state multistep.StateBag) {
|
||||
_, cancelled := state.GetOk(multistep.StateCancelled)
|
||||
_, halted := state.GetOk(multistep.StateHalted)
|
||||
|
||||
if cancelled || halted {
|
||||
config := state.Get("config").(*config)
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
|
||||
ui.Say("Deleting output directory...")
|
||||
for i := 0; i < 5; i++ {
|
||||
err := os.RemoveAll(config.OutputDir)
|
||||
if err == nil {
|
||||
break
|
||||
}
|
||||
|
||||
log.Printf("Error removing output dir: %s", err)
|
||||
time.Sleep(2 * time.Second)
|
||||
}
|
||||
}
|
||||
}
|
100
builder/xenserver/step_shutdown_and_export.go
Normal file
100
builder/xenserver/step_shutdown_and_export.go
Normal file
@ -0,0 +1,100 @@
|
||||
package xenserver
|
||||
|
||||
/* Taken from https://raw.githubusercontent.com/mitchellh/packer/master/builder/qemu/step_prepare_output_dir.go */
|
||||
|
||||
import (
|
||||
"github.com/mitchellh/multistep"
|
||||
"github.com/mitchellh/packer/packer"
|
||||
"os"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"crypto/tls"
|
||||
"io"
|
||||
)
|
||||
|
||||
type stepShutdownAndExport struct{}
|
||||
|
||||
func downloadFile(url, filename string) (err error) {
|
||||
|
||||
// Create the file
|
||||
fh, err := os.Create(filename)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Define a new transport which allows self-signed certs
|
||||
tr := &http.Transport{
|
||||
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
|
||||
}
|
||||
|
||||
// Create a client
|
||||
client := &http.Client{Transport: tr}
|
||||
|
||||
// Create request and download file
|
||||
|
||||
resp, err := client.Get(url)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defer resp.Body.Close()
|
||||
io.Copy(fh, resp.Body)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
func (stepShutdownAndExport) Run(state multistep.StateBag) multistep.StepAction {
|
||||
config := state.Get("config").(config)
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
client := state.Get("client").(XenAPIClient)
|
||||
instance_uuid := state.Get("instance_uuid").(string)
|
||||
|
||||
instance, _ := client.GetVMByUuid(instance_uuid)
|
||||
|
||||
ui.Say("Step: Shutdown and export VPX")
|
||||
|
||||
// Shutdown the VM
|
||||
ui.Say("Shutting down the VM...")
|
||||
instance.CleanShutdown()
|
||||
|
||||
//Export the VM
|
||||
|
||||
export_url := fmt.Sprintf("https://%s/export?vm=%s&session_id=%s",
|
||||
client.Host,
|
||||
instance.Ref,
|
||||
client.Session.(string),
|
||||
)
|
||||
|
||||
export_filename := fmt.Sprintf("%s/%s.xva", config.OutputDir, config.InstanceName)
|
||||
ui.Say("Getting metadata " + export_url)
|
||||
downloadFile(export_url, export_filename)
|
||||
|
||||
disks, _ := instance.GetDisks()
|
||||
for _, disk := range disks {
|
||||
disk_uuid, _ := disk.GetUuid()
|
||||
|
||||
// Basic auth in URL request is required as session token is not
|
||||
// accepted for some reason.
|
||||
// @todo: raise with XAPI team.
|
||||
disk_export_url := fmt.Sprintf("https://%s:%s@%s/export_raw_vdi?vdi=%s",
|
||||
client.Username,
|
||||
client.Password,
|
||||
client.Host,
|
||||
disk_uuid,
|
||||
)
|
||||
|
||||
ui.Say("Getting " + disk_export_url)
|
||||
disk_export_filename := fmt.Sprintf("%s/%s.raw", config.OutputDir, disk_uuid)
|
||||
ui.Say("Downloading " + disk_uuid)
|
||||
downloadFile(disk_export_url, disk_export_filename)
|
||||
}
|
||||
|
||||
|
||||
ui.Say("Download complteded: " + config.OutputDir)
|
||||
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
func (stepShutdownAndExport) Cleanup(state multistep.StateBag) {
|
||||
}
|
30
builder/xenserver/step_start_vm_paused.go
Normal file
30
builder/xenserver/step_start_vm_paused.go
Normal file
@ -0,0 +1,30 @@
|
||||
package xenserver
|
||||
|
||||
|
||||
import (
|
||||
"github.com/mitchellh/multistep"
|
||||
"github.com/mitchellh/packer/packer"
|
||||
)
|
||||
|
||||
type stepStartVmPaused struct {}
|
||||
|
||||
func (self *stepStartVmPaused) Run(state multistep.StateBag) multistep.StepAction {
|
||||
|
||||
client := state.Get("client").(XenAPIClient)
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
|
||||
ui.Say("Step: Start VM Paused")
|
||||
|
||||
instance, _ := client.GetVMByUuid(state.Get("instance_uuid").(string))
|
||||
|
||||
instance.Start(true, false)
|
||||
|
||||
domid, _ := instance.GetDomainId()
|
||||
state.Put("domid", domid)
|
||||
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
|
||||
func (self *stepStartVmPaused) Cleanup(state multistep.StateBag) {
|
||||
}
|
185
builder/xenserver/step_type_boot_command.go
Normal file
185
builder/xenserver/step_type_boot_command.go
Normal file
@ -0,0 +1,185 @@
|
||||
package xenserver
|
||||
|
||||
/* Heavily borrowed from builder/quemu/step_type_boot_command.go */
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/mitchellh/go-vnc"
|
||||
"github.com/mitchellh/multistep"
|
||||
"github.com/mitchellh/packer/packer"
|
||||
"log"
|
||||
"net"
|
||||
"strings"
|
||||
"time"
|
||||
"unicode"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
const KeyLeftShift uint = 0xFFE1
|
||||
|
||||
type bootCommandTemplateData struct {
|
||||
HTTPIP string
|
||||
HTTPPort uint
|
||||
}
|
||||
|
||||
|
||||
type stepTypeBootCommand struct{}
|
||||
|
||||
func (self *stepTypeBootCommand) Run (state multistep.StateBag) multistep.StepAction {
|
||||
config := state.Get("config").(config)
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
vnc_port := state.Get("local_vnc_port").(uint)
|
||||
http_port := state.Get("http_port").(uint)
|
||||
|
||||
// Connect to the local VNC port as we have set up a SSH port forward
|
||||
ui.Say("Connecting to the VM over VNC")
|
||||
net_conn, err := net.Dial("tcp", fmt.Sprintf("127.0.0.1:%d", vnc_port))
|
||||
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Error connecting to VNC: %s", err)
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
defer net_conn.Close()
|
||||
|
||||
c, err := vnc.Client(net_conn, &vnc.ClientConfig{Exclusive: true})
|
||||
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Error establishing VNC session: %s", err)
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
defer c.Close()
|
||||
|
||||
log.Printf("Connected to the VNC console: %s", c.DesktopName)
|
||||
|
||||
// @todo - include http port/ip so kickstarter files can be grabbed
|
||||
tplData := &bootCommandTemplateData {
|
||||
config.LocalIp,
|
||||
http_port,
|
||||
}
|
||||
|
||||
ui.Say("About to type boot commands over VNC...")
|
||||
for _, command := range config.BootCommand {
|
||||
|
||||
command, err := config.tpl.Process(command, tplData)
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Error preparing boot command: %s", err)
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
// Check for interrupts
|
||||
if _, ok := state.GetOk(multistep.StateCancelled); ok {
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
vncSendString(c, command)
|
||||
}
|
||||
|
||||
ui.Say("Finished typing.")
|
||||
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
func (self *stepTypeBootCommand) Cleanup (multistep.StateBag) {}
|
||||
|
||||
// Taken from qemu's builder plugin - not an exported function.
|
||||
func vncSendString(c *vnc.ClientConn, original string) {
|
||||
// Scancodes reference: https://github.com/qemu/qemu/blob/master/ui/vnc_keysym.h
|
||||
special := make(map[string]uint32)
|
||||
special["<bs>"] = 0xFF08
|
||||
special["<del>"] = 0xFFFF
|
||||
special["<enter>"] = 0xFF0D
|
||||
special["<esc>"] = 0xFF1B
|
||||
special["<f1>"] = 0xFFBE
|
||||
special["<f2>"] = 0xFFBF
|
||||
special["<f3>"] = 0xFFC0
|
||||
special["<f4>"] = 0xFFC1
|
||||
special["<f5>"] = 0xFFC2
|
||||
special["<f6>"] = 0xFFC3
|
||||
special["<f7>"] = 0xFFC4
|
||||
special["<f8>"] = 0xFFC5
|
||||
special["<f9>"] = 0xFFC6
|
||||
special["<f10>"] = 0xFFC7
|
||||
special["<f11>"] = 0xFFC8
|
||||
special["<f12>"] = 0xFFC9
|
||||
special["<return>"] = 0xFF0D
|
||||
special["<tab>"] = 0xFF09
|
||||
special["<up>"] = 0xFF52
|
||||
special["<down>"] = 0xFF54
|
||||
special["<left>"] = 0xFF51
|
||||
special["<right>"] = 0xFF53
|
||||
special["<spacebar>"] = 0x020
|
||||
special["<insert>"] = 0xFF63
|
||||
special["<home>"] = 0xFF50
|
||||
special["<end>"] = 0xFF57
|
||||
special["<pageUp>"] = 0xFF55
|
||||
special["<pageDown>"] = 0xFF56
|
||||
|
||||
shiftedChars := "~!@#$%^&*()_+{}|:\"<>?"
|
||||
|
||||
// TODO(mitchellh): Ripe for optimizations of some point, perhaps.
|
||||
for len(original) > 0 {
|
||||
var keyCode uint32
|
||||
keyShift := false
|
||||
|
||||
if strings.HasPrefix(original, "<wait>") {
|
||||
log.Printf("Special code '<wait>' found, sleeping one second")
|
||||
time.Sleep(1 * time.Second)
|
||||
original = original[len("<wait>"):]
|
||||
continue
|
||||
}
|
||||
|
||||
if strings.HasPrefix(original, "<wait5>") {
|
||||
log.Printf("Special code '<wait5>' found, sleeping 5 seconds")
|
||||
time.Sleep(5 * time.Second)
|
||||
original = original[len("<wait5>"):]
|
||||
continue
|
||||
}
|
||||
|
||||
if strings.HasPrefix(original, "<wait10>") {
|
||||
log.Printf("Special code '<wait10>' found, sleeping 10 seconds")
|
||||
time.Sleep(10 * time.Second)
|
||||
original = original[len("<wait10>"):]
|
||||
continue
|
||||
}
|
||||
|
||||
for specialCode, specialValue := range special {
|
||||
if strings.HasPrefix(original, specialCode) {
|
||||
log.Printf("Special code '%s' found, replacing with: %d", specialCode, specialValue)
|
||||
keyCode = specialValue
|
||||
original = original[len(specialCode):]
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if keyCode == 0 {
|
||||
r, size := utf8.DecodeRuneInString(original)
|
||||
original = original[size:]
|
||||
keyCode = uint32(r)
|
||||
keyShift = unicode.IsUpper(r) || strings.ContainsRune(shiftedChars, r)
|
||||
|
||||
log.Printf("Sending char '%c', code %d, shift %v", r, keyCode, keyShift)
|
||||
}
|
||||
|
||||
if keyShift {
|
||||
c.KeyEvent(uint32(KeyLeftShift), true)
|
||||
}
|
||||
|
||||
c.KeyEvent(keyCode, true)
|
||||
c.KeyEvent(keyCode, false)
|
||||
|
||||
if keyShift {
|
||||
c.KeyEvent(uint32(KeyLeftShift), false)
|
||||
}
|
||||
|
||||
// qemu is picky, so no matter what, wait a small period
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
}
|
||||
}
|
79
builder/xenserver/step_upload_iso.go
Normal file
79
builder/xenserver/step_upload_iso.go
Normal file
@ -0,0 +1,79 @@
|
||||
package xenserver
|
||||
|
||||
|
||||
import (
|
||||
"github.com/mitchellh/multistep"
|
||||
"github.com/mitchellh/packer/packer"
|
||||
"os"
|
||||
"strconv"
|
||||
"os/exec"
|
||||
"log"
|
||||
)
|
||||
|
||||
type stepUploadIso struct {}
|
||||
|
||||
func (self *stepUploadIso) Run(state multistep.StateBag) multistep.StepAction {
|
||||
|
||||
client := state.Get("client").(XenAPIClient)
|
||||
config := state.Get("config").(config)
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
|
||||
ui.Say("Step: Upload ISO to server")
|
||||
iso_path := state.Get("iso_path").(string)
|
||||
|
||||
// Determine the ISO's filesize
|
||||
file, err := os.Open(iso_path)
|
||||
if err != nil {
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
stat, err := file.Stat()
|
||||
|
||||
if err != nil {
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
iso_filesize := stat.Size()
|
||||
|
||||
// Create a VDI with the write size
|
||||
sr, err := client.GetSRByUuid(config.SrUuid)
|
||||
|
||||
if err != nil {
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
filesize_str := strconv.FormatInt(iso_filesize, 10)
|
||||
log.Printf("Filesize of the ISO is %d", filesize_str)
|
||||
vdi, err := sr.CreateVdi("Packer Gen " + stat.Name(), filesize_str)
|
||||
|
||||
if err != nil {
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
// Upload the ISO to this VDI
|
||||
vdi_uuid, _ := vdi.GetUuid()
|
||||
host_url := "https://" + client.Host
|
||||
log.Printf("Host URL: %s", host_url)
|
||||
ui.Say("Uploading ISO to " + vdi_uuid)
|
||||
out, err := exec.Command("/usr/bin/importdisk.py", host_url, client.Username, client.Password, vdi_uuid, iso_path).CombinedOutput()
|
||||
log.Printf("Output: %s", out)
|
||||
|
||||
if err != nil {
|
||||
log.Printf("%s", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
// Stash the vdi uuid to be used in preference
|
||||
state.Put("iso_vdi_uuid", vdi_uuid)
|
||||
|
||||
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
|
||||
func (self *stepUploadIso) Cleanup(state multistep.StateBag) {
|
||||
}
|
96
builder/xenserver/step_wait.go
Normal file
96
builder/xenserver/step_wait.go
Normal file
@ -0,0 +1,96 @@
|
||||
package xenserver
|
||||
|
||||
import (
|
||||
"github.com/mitchellh/multistep"
|
||||
"github.com/mitchellh/packer/packer"
|
||||
"time"
|
||||
"reflect"
|
||||
"github.com/nilshell/xmlrpc"
|
||||
)
|
||||
|
||||
|
||||
type stepWait struct{}
|
||||
|
||||
func (self *stepWait) Run(state multistep.StateBag) multistep.StepAction {
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
client := state.Get("client").(XenAPIClient)
|
||||
|
||||
ui.Say("Step: Wait for install to complete.")
|
||||
|
||||
|
||||
//Expect install to be configured to shutdown on completion
|
||||
|
||||
instance_id := state.Get("instance_uuid").(string)
|
||||
instance, _ := client.GetVMByUuid(instance_id)
|
||||
|
||||
for {
|
||||
time.Sleep(30 * time.Second)
|
||||
ui.Say("Waiting for VM install...")
|
||||
|
||||
power_state, _ := instance.GetPowerState()
|
||||
if power_state == "Halted" {
|
||||
ui.Say("Install has completed. Moving on.")
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
//Eject ISO from drive and start VM
|
||||
vbds, _ := instance.GetVBDs()
|
||||
for _, vbd := range vbds {
|
||||
rec, _ := vbd.GetRecord()
|
||||
|
||||
// Hack - should encapsulate this in the client really
|
||||
// This is needed because we can't guarentee the type
|
||||
// returned by the xmlrpc lib will be string
|
||||
switch reflect.TypeOf(rec["type"]).Kind() {
|
||||
case reflect.String:
|
||||
if rec["type"].(string) == "CD" {
|
||||
ui.Say("Ejecting CD...")
|
||||
vbd.Eject()
|
||||
}
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
ui.Say("Starting VM...")
|
||||
instance.Start(false, false)
|
||||
|
||||
|
||||
vm_ip := ""
|
||||
for i:=0; i < 10; i++ {
|
||||
ref, _ := instance.GetGuestMetricsRef()
|
||||
|
||||
if ref != "OpaqueRef:NULL" {
|
||||
metrics, _ := instance.GetGuestMetrics()
|
||||
// todo: xmlrpc shouldn't be needed here
|
||||
networks := metrics["networks"].(xmlrpc.Struct)
|
||||
for k, v := range networks {
|
||||
if k == "0/ip" && v.(string) != "" {
|
||||
vm_ip = v.(string)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Check if an IP has been returned yet
|
||||
if vm_ip != "" {
|
||||
break
|
||||
}
|
||||
|
||||
ui.Say("Wait for IP address...")
|
||||
time.Sleep(10*time.Second)
|
||||
}
|
||||
|
||||
|
||||
// Pass on the VM's IP
|
||||
state.Put("ssh_address", vm_ip)
|
||||
ui.Say("Found the VM's IP " + vm_ip)
|
||||
|
||||
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
func (self *stepWait) Cleanup(state multistep.StateBag) {}
|
||||
|
Loading…
Reference in New Issue
Block a user