github.com/raghuse92/packer@v1.3.2/post-processor/vsphere/post-processor.go (about) 1 package vsphere 2 3 import ( 4 "bytes" 5 "fmt" 6 "io" 7 "log" 8 "net/url" 9 "os" 10 "os/exec" 11 "regexp" 12 "runtime" 13 "strings" 14 15 "github.com/hashicorp/packer/common" 16 "github.com/hashicorp/packer/helper/config" 17 "github.com/hashicorp/packer/packer" 18 "github.com/hashicorp/packer/template/interpolate" 19 ) 20 21 var builtins = map[string]string{ 22 "mitchellh.vmware": "vmware", 23 "mitchellh.vmware-esx": "vmware", 24 } 25 26 var ovftool string = "ovftool" 27 28 var ( 29 // Regular expression to validate RFC1035 hostnames from full fqdn or simple hostname. 30 // For example "packer-esxi1". Requires proper DNS setup and/or correct DNS search domain setting. 31 hostnameRegex = regexp.MustCompile(`^[[:alnum:]][[:alnum:]\-]{0,61}[[:alnum:]]|[[:alpha:]]$`) 32 33 // Simple regular expression to validate IPv4 values. 34 // For example "192.168.1.1". 35 ipv4Regex = regexp.MustCompile(`^(?:[0-9]{1,3}\.){3}[0-9]{1,3}$`) 36 ) 37 38 type Config struct { 39 common.PackerConfig `mapstructure:",squash"` 40 41 Cluster string `mapstructure:"cluster"` 42 Datacenter string `mapstructure:"datacenter"` 43 Datastore string `mapstructure:"datastore"` 44 DiskMode string `mapstructure:"disk_mode"` 45 Host string `mapstructure:"host"` 46 ESXiHost string `mapstructure:"esxi_host"` 47 Insecure bool `mapstructure:"insecure"` 48 Options []string `mapstructure:"options"` 49 Overwrite bool `mapstructure:"overwrite"` 50 Password string `mapstructure:"password"` 51 ResourcePool string `mapstructure:"resource_pool"` 52 Username string `mapstructure:"username"` 53 VMFolder string `mapstructure:"vm_folder"` 54 VMName string `mapstructure:"vm_name"` 55 VMNetwork string `mapstructure:"vm_network"` 56 57 ctx interpolate.Context 58 } 59 60 type PostProcessor struct { 61 config Config 62 } 63 64 func (p *PostProcessor) Configure(raws ...interface{}) error { 65 err := config.Decode(&p.config, &config.DecodeOpts{ 66 Interpolate: true, 67 InterpolateContext: &p.config.ctx, 68 InterpolateFilter: &interpolate.RenderFilter{ 69 Exclude: []string{}, 70 }, 71 }, raws...) 72 if err != nil { 73 return err 74 } 75 76 // Defaults 77 if p.config.DiskMode == "" { 78 p.config.DiskMode = "thick" 79 } 80 81 // Accumulate any errors 82 errs := new(packer.MultiError) 83 84 if runtime.GOOS == "windows" { 85 ovftool = "ovftool.exe" 86 } 87 88 if _, err := exec.LookPath(ovftool); err != nil { 89 errs = packer.MultiErrorAppend( 90 errs, fmt.Errorf("ovftool not found: %s", err)) 91 } 92 93 // First define all our templatable parameters that are _required_ 94 templates := map[string]*string{ 95 "cluster": &p.config.Cluster, 96 "datacenter": &p.config.Datacenter, 97 "diskmode": &p.config.DiskMode, 98 "host": &p.config.Host, 99 "password": &p.config.Password, 100 "username": &p.config.Username, 101 "vm_name": &p.config.VMName, 102 } 103 for key, ptr := range templates { 104 if *ptr == "" { 105 errs = packer.MultiErrorAppend( 106 errs, fmt.Errorf("%s must be set", key)) 107 } 108 } 109 110 if len(errs.Errors) > 0 { 111 return errs 112 } 113 114 return nil 115 } 116 117 func (p *PostProcessor) PostProcess(ui packer.Ui, artifact packer.Artifact) (packer.Artifact, bool, error) { 118 if _, ok := builtins[artifact.BuilderId()]; !ok { 119 return nil, false, fmt.Errorf("Unknown artifact type, can't build box: %s", artifact.BuilderId()) 120 } 121 122 source := "" 123 for _, path := range artifact.Files() { 124 if strings.HasSuffix(path, ".vmx") || strings.HasSuffix(path, ".ovf") || strings.HasSuffix(path, ".ova") { 125 source = path 126 break 127 } 128 } 129 130 if source == "" { 131 return nil, false, fmt.Errorf("VMX, OVF or OVA file not found") 132 } 133 134 password := escapeWithSpaces(p.config.Password) 135 ovftool_uri := fmt.Sprintf("vi://%s:%s@%s/%s/host/%s", 136 escapeWithSpaces(p.config.Username), 137 password, 138 p.config.Host, 139 p.config.Datacenter, 140 p.config.Cluster) 141 142 if p.config.ResourcePool != "" { 143 ovftool_uri += "/Resources/" + p.config.ResourcePool 144 } 145 146 if p.config.ESXiHost != "" { 147 if ipv4Regex.MatchString(p.config.ESXiHost) { 148 ovftool_uri += "/?ip=" + p.config.ESXiHost 149 } else if hostnameRegex.MatchString(p.config.ESXiHost) { 150 ovftool_uri += "/?dns=" + p.config.ESXiHost 151 } 152 } 153 154 args, err := p.BuildArgs(source, ovftool_uri) 155 if err != nil { 156 ui.Message(fmt.Sprintf("Failed: %s\n", err)) 157 } 158 159 ui.Message(fmt.Sprintf("Uploading %s to vSphere", source)) 160 161 log.Printf("Starting ovftool with parameters: %s", 162 strings.Replace( 163 strings.Join(args, " "), 164 password, 165 "<password>", 166 -1)) 167 168 var errWriter io.Writer 169 var errOut bytes.Buffer 170 cmd := exec.Command(ovftool, args...) 171 errWriter = io.MultiWriter(os.Stderr, &errOut) 172 cmd.Stdout = os.Stdout 173 cmd.Stderr = errWriter 174 175 if err := cmd.Run(); err != nil { 176 err := fmt.Errorf("Error uploading virtual machine: %s\n%s\n", err, p.filterLog(errOut.String())) 177 return nil, false, err 178 } 179 180 ui.Message(p.filterLog(errOut.String())) 181 182 artifact = NewArtifact(p.config.Datastore, p.config.VMFolder, p.config.VMName, artifact.Files()) 183 184 return artifact, false, nil 185 } 186 187 func (p *PostProcessor) filterLog(s string) string { 188 password := escapeWithSpaces(p.config.Password) 189 return strings.Replace(s, password, "<password>", -1) 190 } 191 192 func (p *PostProcessor) BuildArgs(source, ovftool_uri string) ([]string, error) { 193 args := []string{ 194 "--acceptAllEulas", 195 fmt.Sprintf(`--name=%s`, p.config.VMName), 196 fmt.Sprintf(`--datastore=%s`, p.config.Datastore), 197 } 198 199 if p.config.Insecure { 200 args = append(args, fmt.Sprintf(`--noSSLVerify=%t`, p.config.Insecure)) 201 } 202 203 if p.config.DiskMode != "" { 204 args = append(args, fmt.Sprintf(`--diskMode=%s`, p.config.DiskMode)) 205 } 206 207 if p.config.VMFolder != "" { 208 args = append(args, fmt.Sprintf(`--vmFolder=%s`, p.config.VMFolder)) 209 } 210 211 if p.config.VMNetwork != "" { 212 args = append(args, fmt.Sprintf(`--network=%s`, p.config.VMNetwork)) 213 } 214 215 if p.config.Overwrite == true { 216 args = append(args, "--overwrite") 217 } 218 219 if len(p.config.Options) > 0 { 220 args = append(args, p.config.Options...) 221 } 222 223 args = append(args, fmt.Sprintf(`%s`, source)) 224 args = append(args, fmt.Sprintf(`%s`, ovftool_uri)) 225 226 return args, nil 227 } 228 229 // Encode everything except for + signs 230 func escapeWithSpaces(stringToEscape string) string { 231 escapedString := url.QueryEscape(stringToEscape) 232 escapedString = strings.Replace(escapedString, "+", `%20`, -1) 233 return escapedString 234 }