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"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"errors"
|
"errors"
|
||||||
|
"time"
|
||||||
|
"strings"
|
||||||
|
"os"
|
||||||
|
commonssh "github.com/mitchellh/packer/common/ssh"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -17,12 +21,46 @@ const BuilderId = "packer.xenserver"
|
|||||||
type config struct {
|
type config struct {
|
||||||
common.PackerConfig `mapstructure:",squash"`
|
common.PackerConfig `mapstructure:",squash"`
|
||||||
|
|
||||||
Username string `mapstructure:"username"`
|
Username string `mapstructure:"username"`
|
||||||
Password string `mapstructure:"password"`
|
Password string `mapstructure:"password"`
|
||||||
HostIp string `mapstructure:"host_ip"`
|
HostIp string `mapstructure:"host_ip"`
|
||||||
IsoUrl string `mapstructure:"iso_url"`
|
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
|
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...)
|
md, err := common.DecodeConfig(&self.config, raws...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
errs := common.CheckUnusedConfig(md)
|
||||||
|
if errs == nil {
|
||||||
|
errs = &packer.MultiError{}
|
||||||
|
}
|
||||||
|
|
||||||
self.config.tpl, err = packer.NewConfigTemplate()
|
self.config.tpl, err = packer.NewConfigTemplate()
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
self.config.tpl.UserVars = self.config.PackerUserVars
|
|
||||||
|
|
||||||
|
|
||||||
errs := common.CheckUnusedConfig(md)
|
|
||||||
|
|
||||||
|
|
||||||
// Set default vaules
|
// 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 {
|
templates := map[string]*string {
|
||||||
"username": &self.config.Username,
|
"username": &self.config.Username,
|
||||||
"password": &self.config.Password,
|
"password": &self.config.Password,
|
||||||
"host_ip": &self.config.HostIp,
|
"host_ip": &self.config.HostIp,
|
||||||
"iso_url": &self.config.IsoUrl,
|
"iso_url": &self.config.IsoUrl,
|
||||||
"instance_name": &self.config.InstanceName,
|
"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 == "" {
|
if self.config.IsoUrl == "" {
|
||||||
errs = packer.MultiErrorAppend(
|
errs = packer.MultiErrorAppend(
|
||||||
errs, errors.New("a iso url must be specified"))
|
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) {
|
func (self *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packer.Artifact, error) {
|
||||||
log.Println("Run, not yet implemented!")
|
//Setup XAPI client
|
||||||
return nil, nil
|
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