1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153
|
//go:generate mapstructure-to-hcl2 -type Config
package vsphere_template
import (
"context"
"errors"
"fmt"
"net/url"
"strings"
"time"
"github.com/hashicorp/hcl/v2/hcldec"
vmwcommon "github.com/hashicorp/packer/builder/vmware/common"
vsphere "github.com/hashicorp/packer/builder/vsphere/common"
"github.com/hashicorp/packer/packer-plugin-sdk/common"
"github.com/hashicorp/packer/packer-plugin-sdk/multistep"
"github.com/hashicorp/packer/packer-plugin-sdk/multistep/commonsteps"
packersdk "github.com/hashicorp/packer/packer-plugin-sdk/packer"
"github.com/hashicorp/packer/packer-plugin-sdk/template/config"
"github.com/hashicorp/packer/packer-plugin-sdk/template/interpolate"
"github.com/hashicorp/packer/post-processor/artifice"
vspherepost "github.com/hashicorp/packer/post-processor/vsphere"
"github.com/vmware/govmomi"
)
var builtins = map[string]string{
vspherepost.BuilderId: "vmware",
vmwcommon.BuilderIdESX: "vmware",
vsphere.BuilderId: "vsphere",
artifice.BuilderId: "artifice",
}
type Config struct {
common.PackerConfig `mapstructure:",squash"`
Host string `mapstructure:"host"`
Insecure bool `mapstructure:"insecure"`
Username string `mapstructure:"username"`
Password string `mapstructure:"password"`
Datacenter string `mapstructure:"datacenter"`
Folder string `mapstructure:"folder"`
SnapshotEnable bool `mapstructure:"snapshot_enable"`
SnapshotName string `mapstructure:"snapshot_name"`
SnapshotDescription string `mapstructure:"snapshot_description"`
ReregisterVM config.Trilean `mapstructure:"reregister_vm"`
ctx interpolate.Context
}
type PostProcessor struct {
config Config
url *url.URL
}
func (p *PostProcessor) ConfigSpec() hcldec.ObjectSpec { return p.config.FlatMapstructure().HCL2Spec() }
func (p *PostProcessor) Configure(raws ...interface{}) error {
err := config.Decode(&p.config, &config.DecodeOpts{
PluginType: vsphere.BuilderId,
Interpolate: true,
InterpolateContext: &p.config.ctx,
InterpolateFilter: &interpolate.RenderFilter{
Exclude: []string{},
},
}, raws...)
if err != nil {
return err
}
errs := new(packersdk.MultiError)
vc := map[string]*string{
"host": &p.config.Host,
"username": &p.config.Username,
"password": &p.config.Password,
}
for key, ptr := range vc {
if *ptr == "" {
errs = packersdk.MultiErrorAppend(
errs, fmt.Errorf("%s must be set", key))
}
}
if p.config.Folder != "" && !strings.HasPrefix(p.config.Folder, "/") {
errs = packersdk.MultiErrorAppend(
errs, fmt.Errorf("Folder must be bound to the root"))
}
sdk, err := url.Parse(fmt.Sprintf("https://%v/sdk", p.config.Host))
if err != nil {
errs = packersdk.MultiErrorAppend(
errs, fmt.Errorf("Error invalid vSphere sdk endpoint: %s", err))
return errs
}
sdk.User = url.UserPassword(p.config.Username, p.config.Password)
p.url = sdk
if len(errs.Errors) > 0 {
return errs
}
return nil
}
func (p *PostProcessor) PostProcess(ctx context.Context, ui packersdk.Ui, artifact packersdk.Artifact) (packersdk.Artifact, bool, bool, error) {
if _, ok := builtins[artifact.BuilderId()]; !ok {
return nil, false, false, fmt.Errorf("The Packer vSphere Template post-processor "+
"can only take an artifact from the VMware-iso builder, built on "+
"ESXi (i.e. remote) or an artifact from the vSphere post-processor. "+
"Artifact type %s does not fit this requirement", artifact.BuilderId())
}
f := artifact.State(vmwcommon.ArtifactConfFormat)
k := artifact.State(vmwcommon.ArtifactConfKeepRegistered)
s := artifact.State(vmwcommon.ArtifactConfSkipExport)
if f != "" && k != "true" && s == "false" {
return nil, false, false, errors.New("To use this post-processor with exporting behavior you need set keep_registered as true")
}
// In some occasions the VM state is powered on and if we immediately try to mark as template
// (after the ESXi creates it) it will fail. If vSphere is given a few seconds this behavior doesn't reappear.
ui.Message("Waiting 10s for VMware vSphere to start")
time.Sleep(10 * time.Second)
c, err := govmomi.NewClient(context.Background(), p.url, p.config.Insecure)
if err != nil {
return nil, false, false, fmt.Errorf("Error connecting to vSphere: %s", err)
}
defer c.Logout(context.Background())
state := new(multistep.BasicStateBag)
state.Put("ui", ui)
state.Put("client", c)
steps := []multistep.Step{
&stepChooseDatacenter{
Datacenter: p.config.Datacenter,
},
&stepCreateFolder{
Folder: p.config.Folder,
},
NewStepCreateSnapshot(artifact, p),
NewStepMarkAsTemplate(artifact, p),
}
runner := commonsteps.NewRunnerWithPauseFn(steps, p.config.PackerConfig, ui, state)
runner.Run(ctx, state)
if rawErr, ok := state.GetOk("error"); ok {
return nil, false, false, rawErr.(error)
}
return artifact, true, true, nil
}
|