Merge branch 'master' into 'ephemeral-ssh-key-pair-issue-7225'.

This commit is contained in:
Stephen Fox 2019-02-04 12:47:42 -05:00
commit ea2a7c3fe9
24 changed files with 393 additions and 248 deletions

View File

@ -34,7 +34,7 @@ type driverGCE struct {
var DriverScopes = []string{"https://www.googleapis.com/auth/compute", "https://www.googleapis.com/auth/devstorage.full_control"}
func NewDriverGCE(ui packer.Ui, p string, a *AccountFile) (Driver, error) {
func NewClientGCE(a *AccountFile) (*http.Client, error) {
var err error
var client *http.Client
@ -78,6 +78,15 @@ func NewDriverGCE(ui packer.Ui, p string, a *AccountFile) (Driver, error) {
return nil, err
}
return client, nil
}
func NewDriverGCE(ui packer.Ui, p string, a *AccountFile) (Driver, error) {
client, err := NewClientGCE(a)
if err != nil {
return nil, err
}
log.Printf("[INFO] Instantiating GCE client...")
service, err := compute.New(client)
if err != nil {

View File

@ -15,7 +15,7 @@ const StartupScriptStatusNotDone string = "notdone"
var StartupScriptLinux string = fmt.Sprintf(`#!/usr/bin/env bash
echo "Packer startup script starting."
RETVAL=0
BASEMETADATAURL=http://metadata/computeMetadata/v1/instance/
BASEMETADATAURL=http://metadata.google.internal/computeMetadata/v1/instance/
GetMetadata () {
echo "$(curl -f -H "Metadata-Flavor: Google" ${BASEMETADATAURL}/${1} 2> /dev/null)"

View File

@ -61,9 +61,9 @@ func (s *StepCreateVM) Run(_ context.Context, state multistep.StateBag) multiste
}
// convert the MB to bytes
ramSize := int64(s.RamSize * 1024 * 1024)
diskSize := int64(s.DiskSize * 1024 * 1024)
diskBlockSize := int64(s.DiskBlockSize * 1024 * 1024)
ramSize := int64(s.RamSize) * 1024 * 1024
diskSize := int64(s.DiskSize) * 1024 * 1024
diskBlockSize := int64(s.DiskBlockSize) * 1024 * 1024
err := driver.CreateVirtualMachine(s.VMName, path, harddrivePath, ramSize, diskSize, diskBlockSize,
s.SwitchName, s.Generation, s.DifferencingDisk, s.FixedVHD, s.Version)

View File

@ -14,6 +14,7 @@ type HWConfig struct {
// cpu information
CpuCount int `mapstructure:"cpus"`
MemorySize int `mapstructure:"memory"`
CoreCount int `mapstructure:"cores"`
// network type and adapter
Network string `mapstructure:"network"`
@ -40,6 +41,11 @@ func (c *HWConfig) Prepare(ctx *interpolate.Context) []error {
errs = append(errs, fmt.Errorf("An invalid amount of memory was specified (memory < 0): %d", c.MemorySize))
}
// Hardware and cpu options
if c.CoreCount < 0 {
errs = append(errs, fmt.Errorf("An invalid number of cores was specified (cores < 0): %d", c.CoreCount))
}
// Peripherals
if !c.Sound {
c.Sound = false

View File

@ -8,6 +8,7 @@ import (
func testHWConfig() *HWConfig {
return &HWConfig{
CpuCount: 1,
CoreCount: 1,
MemorySize: 512,
Sound: true,
@ -26,6 +27,10 @@ func TestHWConfigPrepare(t *testing.T) {
t.Errorf("bad cpu count: %d", c.CpuCount)
}
if c.CoreCount < 0 {
t.Errorf("bad core count: %d", c.CoreCount)
}
if c.MemorySize < 0 {
t.Errorf("bad memory size: %d", c.MemorySize)
}

View File

@ -423,12 +423,19 @@ func (s *stepCreateVMX) Run(_ context.Context, state multistep.StateBag) multist
s.tempDir = vmxDir
}
/// Now to handle options that will modify the template
/// Now to handle options that will modify the template without using "vmxTemplateData"
vmxData := vmwcommon.ParseVMX(vmxContents)
// If no cpus were specified, then remove the entry to use the default
if vmxData["numvcpus"] == "" {
delete(vmxData, "numvcpus")
}
// If some number of cores were specified, then update "cpuid.coresPerSocket" with the requested value
if config.HWConfig.CoreCount > 0 {
vmxData["cpuid.corespersocket"] = strconv.Itoa(config.HWConfig.CoreCount)
}
/// Write the vmxData to the vmxPath
vmxPath := filepath.Join(vmxDir, config.VMName+".vmx")
if err := vmwcommon.WriteVMX(vmxPath, vmxData); err != nil {

View File

@ -37,6 +37,10 @@ func TestBuildOnlyFileCommaFlags(t *testing.T) {
if fileExists("cherry.txt") {
t.Error("Expected NOT to find cherry.txt")
}
if !fileExists("tomato.txt") {
t.Error("Expected to find tomato.txt")
}
}
func TestBuildStdin(t *testing.T) {
@ -104,7 +108,7 @@ func TestBuildExceptFileCommaFlags(t *testing.T) {
}
args := []string{
"-except=chocolate,apple",
"-except=chocolate,vanilla",
filepath.Join(testFixture("build-only"), "template.json"),
}
@ -114,12 +118,12 @@ func TestBuildExceptFileCommaFlags(t *testing.T) {
fatalCommand(t, c.Meta)
}
for _, f := range []string{"chocolate.txt", "apple.txt", "peach.txt"} {
for _, f := range []string{"chocolate.txt", "vanilla.txt", "tomato.txt"} {
if fileExists(f) {
t.Errorf("Expected NOT to find %s", f)
}
}
for _, f := range []string{"vanilla.txt", "cherry.txt", "pear.txt"} {
for _, f := range []string{"apple.txt", "cherry.txt", "pear.txt", "peach.txt"} {
if !fileExists(f) {
t.Errorf("Expected to find %s", f)
}
@ -169,4 +173,5 @@ func cleanup() {
os.RemoveAll("apple.txt")
os.RemoveAll("peach.txt")
os.RemoveAll("pear.txt")
os.RemoveAll("tomato.txt")
}

View File

@ -38,6 +38,16 @@
"type": "shell-local",
"inline": [ "touch pear.txt" ]
}
],
[
{
"only": [
"vanilla"
],
"name": "tomato",
"type": "shell-local",
"inline": [ "touch tomato.txt" ]
}
]
]
}

14
go.sum
View File

@ -1,3 +1,4 @@
cloud.google.com/go v0.26.0 h1:e0WKqKTd5BnrG8aKH3J3h+QvEIQtSUcf2n5UZ5ZgLtQ=
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
github.com/1and1/oneandone-cloudserver-sdk-go v1.0.1 h1:RMTyvS5bjvSWiUcfqfr/E2pxHEMrALvU+E12n6biymg=
github.com/1and1/oneandone-cloudserver-sdk-go v1.0.1/go.mod h1:61apmbkVJH4kg+38ftT+/l0XxdUCVnHggqcOTqZRSEE=
@ -80,7 +81,7 @@ github.com/digitalocean/godo v0.0.0-20170407151542-4c04abe183f4 h1:34XBbvedApvUM
github.com/digitalocean/godo v0.0.0-20170407151542-4c04abe183f4/go.mod h1:h6faOIcZ8lWIwNQ+DN7b3CgX4Kwby5T+nbpNqkUIozU=
github.com/dnaeon/go-vcr v1.0.0 h1:1QZ+ahihvRvppcJnFvuoHAdnZTf1PqKjO4Ftr1cfQTo=
github.com/dnaeon/go-vcr v1.0.0/go.mod h1:aBB1+wY4s93YsC3HHjMBMrwTj2R9FHDzUr9KyGc8n1E=
github.com/docker/docker v0.0.0-20170406124027-fa3e2d5ab9b5 h1:V/WghsgVPZl+92drOvPEwuZdHskJmrnD83V1msaV/Jc=
github.com/docker/docker v0.0.0-20170406124027-fa3e2d5ab9b5 h1:DwY2bFs8p+xf2WaQewx2hnGdjYR5K4UAaxcNPyKkTek=
github.com/docker/docker v0.0.0-20170406124027-fa3e2d5ab9b5/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ=
github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec=
@ -140,7 +141,7 @@ github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e h1:JKmoR8x90Iww1
github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gorilla/websocket v0.0.0-20170319172727-a91eba7f9777 h1:JIM+OacoOJRU30xpjMf8sulYqjr0ViA3WDrTX6j/yDI=
github.com/gorilla/websocket v0.0.0-20170319172727-a91eba7f9777/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
github.com/gotestyourself/gotestyourself v2.2.0+incompatible h1:1yOKgt0XYKUg1HOKunGOSt2ocU4bxLCjmIHt0vRtVHM=
github.com/gotestyourself/gotestyourself v2.2.0+incompatible h1:AQwinXlbQR2HvPjQZOmDhRqsv5mZf+Jb1RnSLxcqZcI=
github.com/gotestyourself/gotestyourself v2.2.0+incompatible/go.mod h1:zZKM6oeNM8k+FRljX1mnzVYeS8wiGgQyvST1/GafPbY=
github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed h1:5upAirOpQc1Q53c0bnx2ufif5kANL7bfZWcc6VJWJd8=
github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed/go.mod h1:tMWxXQ9wFIaZeTI9F+hmhFiGpFmhOHzyShyFUhRm0H4=
@ -169,7 +170,7 @@ github.com/hashicorp/go-plugin v0.0.0-20181030172320-54b6ff97d818 h1:wA1XRGBHMdp
github.com/hashicorp/go-plugin v0.0.0-20181030172320-54b6ff97d818/go.mod h1:Ft7ju2vWzhO0ETMKUVo12XmXmII6eSUS4rsPTkY/siA=
github.com/hashicorp/go-retryablehttp v0.0.0-20180718195005-e651d75abec6 h1:qCv4319q2q7XKn0MQbi8p37hsJ+9Xo8e6yojA73JVxk=
github.com/hashicorp/go-retryablehttp v0.0.0-20180718195005-e651d75abec6/go.mod h1:fXcdFsQoipQa7mwORhKad5jmDCeSy/RCGzWA08PO0lM=
github.com/hashicorp/go-rootcerts v0.0.0-20160503143440-6bb64b370b90 h1:9HVkPxOpo+yO93Ah4yrO67d/qh0fbLLWbKqhYjyHq9A=
github.com/hashicorp/go-rootcerts v0.0.0-20160503143440-6bb64b370b90 h1:VBj0QYQ0u2MCJzBfeYXGexnAl17GsH1yidnoxCqqD9E=
github.com/hashicorp/go-rootcerts v0.0.0-20160503143440-6bb64b370b90/go.mod h1:o4zcYY1e0GEZI6eSEr+43QDYmuGglw1qSO6qdHUHCgg=
github.com/hashicorp/go-sockaddr v0.0.0-20180320115054-6d291a969b86 h1:7YOlAIO2YWnJZkQp7B5eFykaIY7C9JndqAFQyVV5BhM=
github.com/hashicorp/go-sockaddr v0.0.0-20180320115054-6d291a969b86/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU=
@ -357,22 +358,27 @@ github.com/vmware/govmomi v0.0.0-20170707011325-c2105a174311 h1:s5pyxd5S6wRs2WpE
github.com/vmware/govmomi v0.0.0-20170707011325-c2105a174311/go.mod h1:URlwyTFZX72RmxtxuaFL2Uj3fD1JTvZdx59bHWk6aFU=
github.com/xanzy/go-cloudstack v2.1.4+incompatible h1:5c72sRFakVv8wH/HsQFg+xr37CmNQU2UbJfaBjW5f0c=
github.com/xanzy/go-cloudstack v2.1.4+incompatible/go.mod h1:s3eL3z5pNXF5FVybcT+LIVdId8pYn709yv6v5mrkrQE=
golang.org/x/crypto v0.0.0-20180322175230-88942b9c40a4 h1:AJCW0rhPjFKEAoValWpqnRKxX8YV0Xvqfw+dOexCTPc=
golang.org/x/crypto v0.0.0-20180322175230-88942b9c40a4/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181201002055-351d144fa1fc h1:a3CU5tJYVj92DY2LaA1kUkrsqD5/3mLDhx2NcNqyW+0=
golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be h1:vEDujvNQGv4jgYKudGeI/+DAX4Jffq6hpD55MmoEvKs=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f h1:Bl/8QSvNqXvPGPGXa2z5xUTmV7VDcZyvRZ+QQXkXTZQ=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e h1:o3PsSEY8E4eXWkXrIP9YJALUkVZqzHJT5DOasTyn8Vs=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2 h1:+DCIGbF/swA92ohVg0//6X2IVY3KZs6p9mix0ziNYJM=
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
google.golang.org/api v0.0.0-20180818000503-e21acd801f91 h1:MgYYgjaWMS2qQiDwCznfbqNmEOdSULlvjCvSCvIe/Wo=
google.golang.org/api v0.0.0-20180818000503-e21acd801f91/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=
google.golang.org/appengine v1.1.0 h1:igQkv0AAhEIvTEpD5LIpAfav2eeVO9HBTjvKHVJPRSs=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
@ -407,7 +413,7 @@ gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkep
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/vmihailenco/msgpack.v2 v2.9.1 h1:kb0VV7NuIojvRfzwslQeP3yArBqJHW9tOl4t38VS1jM=
gopkg.in/vmihailenco/msgpack.v2 v2.9.1/go.mod h1:/3Dn1Npt9+MYyLpYYXjInO/5jvMLamn+AEGwNEOatn8=
gopkg.in/yaml.v2 v2.2.1 h1:mUhvW9EsL+naU5Q3cakzfE91YhliOondGd6ZrsDBHQE=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gotest.tools v2.2.0+incompatible h1:y0IMTfclpMdsdIbr6uwmJn5/WZ7vFuObxDMdrylFM3A=
gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw=
honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=

View File

@ -180,13 +180,18 @@ func (c *Core) Build(n string) (Build, error) {
for _, rawPs := range c.Template.PostProcessors {
current := make([]coreBuildPostProcessor, 0, len(rawPs))
for _, rawP := range rawPs {
// If we skip, ignore
rawP.OnlyExcept.Except = append(rawP.OnlyExcept.Except, c.except...)
if rawP.OnlyExcept.Skip(rawName) {
if rawP.Skip(rawName) {
continue
}
if rawP.OnlyExcept.Skip(rawP.Name) {
break
// -except skips post-processor & build
foundExcept := false
for _, except := range c.except {
if except == rawP.Name {
foundExcept = true
}
}
if foundExcept {
continue
}
// Get the post-processor

View File

@ -15,10 +15,19 @@ import (
type Config struct {
common.PackerConfig `mapstructure:",squash"`
Paths []string `mapstructure:"paths"`
KeepOriginalImage bool `mapstructure:"keep_input_artifact"`
AccountFile string `mapstructure:"account_file"`
ctx interpolate.Context
DiskSizeGb int64 `mapstructure:"disk_size"`
DiskType string `mapstructure:"disk_type"`
KeepOriginalImage bool `mapstructure:"keep_input_artifact"`
MachineType string `mapstructure:"machine_type"`
Network string `mapstructure:"network"`
Paths []string `mapstructure:"paths"`
Subnetwork string `mapstructure:"subnetwork"`
Zone string `mapstructure:"zone"`
Account googlecompute.AccountFile
ctx interpolate.Context
}
type PostProcessor struct {
@ -35,12 +44,38 @@ func (p *PostProcessor) Configure(raws ...interface{}) error {
return err
}
errs := new(packer.MultiError)
if len(p.config.Paths) == 0 {
errs = packer.MultiErrorAppend(
errs, fmt.Errorf("paths must be specified"))
}
// Set defaults.
if p.config.DiskSizeGb == 0 {
p.config.DiskSizeGb = 200
}
if p.config.DiskType == "" {
p.config.DiskType = "pd-ssd"
}
if p.config.MachineType == "" {
p.config.MachineType = "n1-highcpu-4"
}
if p.config.Network == "" && p.config.Subnetwork == "" {
p.config.Network = "default"
}
if len(errs.Errors) > 0 {
return errs
}
return nil
}
func (p *PostProcessor) PostProcess(ui packer.Ui, artifact packer.Artifact) (packer.Artifact, bool, error) {
ui.Say("Starting googlecompute-export...")
ui.Say(fmt.Sprintf("Exporting image to destinations: %v", p.config.Paths))
if artifact.BuilderId() != googlecompute.BuilderId {
err := fmt.Errorf(
"Unknown artifact type: %s\nCan only export from Google Compute Engine builder artifacts.",
@ -48,79 +83,90 @@ func (p *PostProcessor) PostProcess(ui packer.Ui, artifact packer.Artifact) (pac
return nil, p.config.KeepOriginalImage, err
}
result := &Artifact{paths: p.config.Paths}
builderAccountFile := artifact.State("AccountFilePath").(string)
builderImageName := artifact.State("ImageName").(string)
builderProjectId := artifact.State("ProjectId").(string)
builderZone := artifact.State("BuildZone").(string)
if len(p.config.Paths) > 0 {
accountKeyFilePath := artifact.State("AccountFilePath").(string)
imageName := artifact.State("ImageName").(string)
imageSizeGb := artifact.State("ImageSizeGb").(int64)
projectId := artifact.State("ProjectId").(string)
zone := artifact.State("BuildZone").(string)
ui.Say(fmt.Sprintf("Exporting image %v to destination: %v", builderImageName, p.config.Paths))
// Set up instance configuration.
instanceName := fmt.Sprintf("%s-exporter", artifact.Id())
metadata := map[string]string{
"image_name": imageName,
"name": instanceName,
"paths": strings.Join(p.config.Paths, " "),
"startup-script": StartupScript,
"zone": zone,
}
exporterConfig := googlecompute.Config{
InstanceName: instanceName,
SourceImageProjectId: "debian-cloud",
SourceImage: "debian-8-jessie-v20160629",
DiskName: instanceName,
DiskSizeGb: imageSizeGb + 10,
DiskType: "pd-standard",
Metadata: metadata,
MachineType: "n1-standard-4",
Zone: zone,
Network: "default",
RawStateTimeout: "5m",
Scopes: []string{
"https://www.googleapis.com/auth/userinfo.email",
"https://www.googleapis.com/auth/compute",
"https://www.googleapis.com/auth/devstorage.full_control",
},
}
exporterConfig.CalcTimeout()
if p.config.Zone == "" {
p.config.Zone = builderZone
}
// Set up credentials and GCE driver.
if accountKeyFilePath != "" {
err := googlecompute.ProcessAccountFile(&exporterConfig.Account, accountKeyFilePath)
if err != nil {
return nil, p.config.KeepOriginalImage, err
}
}
driver, err := googlecompute.NewDriverGCE(ui, projectId, &exporterConfig.Account)
// Set up credentials for GCE driver.
if builderAccountFile != "" {
err := googlecompute.ProcessAccountFile(&p.config.Account, builderAccountFile)
if err != nil {
return nil, p.config.KeepOriginalImage, err
}
// Set up the state.
state := new(multistep.BasicStateBag)
state.Put("config", &exporterConfig)
state.Put("driver", driver)
state.Put("ui", ui)
// Build the steps.
steps := []multistep.Step{
&googlecompute.StepCreateSSHKey{
Debug: p.config.PackerDebug,
DebugKeyPath: fmt.Sprintf("gce_%s.pem", p.config.PackerBuildName),
},
&googlecompute.StepCreateInstance{
Debug: p.config.PackerDebug,
},
new(googlecompute.StepWaitStartupScript),
new(googlecompute.StepTeardownInstance),
}
// Run the steps.
p.runner = common.NewRunner(steps, p.config.PackerConfig, ui)
p.runner.Run(state)
}
if p.config.AccountFile != "" {
err := googlecompute.ProcessAccountFile(&p.config.Account, p.config.AccountFile)
if err != nil {
return nil, p.config.KeepOriginalImage, err
}
}
// Set up exporter instance configuration.
exporterName := fmt.Sprintf("%s-exporter", artifact.Id())
exporterMetadata := map[string]string{
"image_name": builderImageName,
"name": exporterName,
"paths": strings.Join(p.config.Paths, " "),
"startup-script": StartupScript,
"zone": p.config.Zone,
}
exporterConfig := googlecompute.Config{
DiskName: exporterName,
DiskSizeGb: p.config.DiskSizeGb,
DiskType: p.config.DiskType,
InstanceName: exporterName,
MachineType: p.config.MachineType,
Metadata: exporterMetadata,
Network: p.config.Network,
RawStateTimeout: "5m",
SourceImageFamily: "debian-9-worker",
SourceImageProjectId: "compute-image-tools",
Subnetwork: p.config.Subnetwork,
Zone: p.config.Zone,
Scopes: []string{
"https://www.googleapis.com/auth/compute",
"https://www.googleapis.com/auth/devstorage.full_control",
"https://www.googleapis.com/auth/userinfo.email",
},
}
exporterConfig.CalcTimeout()
driver, err := googlecompute.NewDriverGCE(ui, builderProjectId, &p.config.Account)
if err != nil {
return nil, p.config.KeepOriginalImage, err
}
// Set up the state.
state := new(multistep.BasicStateBag)
state.Put("config", &exporterConfig)
state.Put("driver", driver)
state.Put("ui", ui)
// Build the steps.
steps := []multistep.Step{
&googlecompute.StepCreateSSHKey{
Debug: p.config.PackerDebug,
DebugKeyPath: fmt.Sprintf("gce_%s.pem", p.config.PackerBuildName),
},
&googlecompute.StepCreateInstance{
Debug: p.config.PackerDebug,
},
new(googlecompute.StepWaitStartupScript),
new(googlecompute.StepTeardownInstance),
}
// Run the steps.
p.runner = common.NewRunner(steps, p.config.PackerConfig, ui)
p.runner.Run(state)
result := &Artifact{paths: p.config.Paths}
return result, p.config.KeepOriginalImage, nil
}

View File

@ -1,6 +1,6 @@
package googlecomputeexport
var StartupScript string = `#!/bin/sh
var StartupScript string = `#!/bin/bash
GetMetadata () {
echo "$(curl -f -H "Metadata-Flavor: Google" http://metadata/computeMetadata/v1/instance/attributes/$1 2> /dev/null)"
@ -8,11 +8,11 @@ GetMetadata () {
IMAGENAME=$(GetMetadata image_name)
NAME=$(GetMetadata name)
DISKNAME=${NAME}-toexport
PATHS=$(GetMetadata paths)
PATHS=($(GetMetadata paths))
ZONE=$(GetMetadata zone)
Exit () {
for i in ${PATHS}; do
for i in ${PATHS[@]}; do
LOGDEST="${i}.exporter.log"
echo "Uploading exporter log to ${LOGDEST}..."
gsutil -h "Content-Type:text/plain" cp /var/log/daemon.log ${LOGDEST}
@ -40,17 +40,15 @@ if ! gcloud compute instances attach-disk ${NAME} --disk ${DISKNAME} --device-na
Exit 1
fi
echo "Dumping disk..."
if ! dd if=/dev/disk/by-id/google-toexport of=disk.raw bs=4096 conv=sparse; then
echo "Failed to dump disk to image."
echo "GCEExport: Running export tool."
gce_export -gcs_path "${PATHS[0]}" -disk /dev/disk/by-id/google-toexport -y
if [ $? -ne 0 ]; then
echo "ExportFailed: Failed to export disk source to ${PATHS[0]}."
Exit 1
fi
echo "Compressing and tar'ing disk image..."
if ! tar -czf root.tar.gz disk.raw; then
echo "Failed to tar disk image."
Exit 1
fi
echo "ExportSuccess"
sync
echo "Detaching disk..."
if ! gcloud compute instances detach-disk ${NAME} --disk ${DISKNAME} --zone ${ZONE}; then
@ -64,10 +62,10 @@ if ! gcloud compute disks delete ${DISKNAME} --zone ${ZONE}; then
FAIL=1
fi
for i in ${PATHS}; do
echo "Uploading tar'ed disk image to ${i}..."
if ! gsutil -o GSUtil:parallel_composite_upload_threshold=100M cp root.tar.gz ${i}; then
echo "Failed to upload image to ${i}."
for i in ${PATHS[@]:1}; do
echo "Copying archive image to ${i}..."
if ! gsutil -o GSUtil:parallel_composite_upload_threshold=100M cp ${PATHS[0]} ${i}; then
echo "Failed to copy image to ${i}."
FAIL=1
fi
done

View File

@ -13,13 +13,9 @@ import (
"github.com/hashicorp/packer/builder/googlecompute"
"github.com/hashicorp/packer/common"
"github.com/hashicorp/packer/helper/config"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
"github.com/hashicorp/packer/post-processor/compress"
"github.com/hashicorp/packer/template/interpolate"
"golang.org/x/oauth2"
"golang.org/x/oauth2/jwt"
)
type Config struct {
@ -38,12 +34,12 @@ type Config struct {
KeepOriginalImage bool `mapstructure:"keep_input_artifact"`
SkipClean bool `mapstructure:"skip_clean"`
ctx interpolate.Context
Account googlecompute.AccountFile
ctx interpolate.Context
}
type PostProcessor struct {
config Config
runner multistep.Runner
}
func (p *PostProcessor) Configure(raws ...interface{}) error {
@ -60,24 +56,29 @@ func (p *PostProcessor) Configure(raws ...interface{}) error {
return err
}
errs := new(packer.MultiError)
// Set defaults
if p.config.GCSObjectName == "" {
p.config.GCSObjectName = "packer-import-{{timestamp}}.tar.gz"
}
errs := new(packer.MultiError)
// Check and render gcs_object_name
if err = interpolate.Validate(p.config.GCSObjectName, &p.config.ctx); err != nil {
errs = packer.MultiErrorAppend(
errs, fmt.Errorf("Error parsing gcs_object_name template: %s", err))
}
if p.config.AccountFile != "" {
if err := googlecompute.ProcessAccountFile(&p.config.Account, p.config.AccountFile); err != nil {
errs = packer.MultiErrorAppend(errs, err)
}
}
templates := map[string]*string{
"bucket": &p.config.Bucket,
"image_name": &p.config.ImageName,
"project_id": &p.config.ProjectId,
"account_file": &p.config.AccountFile,
"bucket": &p.config.Bucket,
"image_name": &p.config.ImageName,
"project_id": &p.config.ProjectId,
}
for key, ptr := range templates {
if *ptr == "" {
@ -94,7 +95,10 @@ func (p *PostProcessor) Configure(raws ...interface{}) error {
}
func (p *PostProcessor) PostProcess(ui packer.Ui, artifact packer.Artifact) (packer.Artifact, bool, error) {
var err error
client, err := googlecompute.NewClientGCE(&p.config.Account)
if err != nil {
return nil, false, err
}
if artifact.BuilderId() != compress.BuilderId {
err = fmt.Errorf(
@ -108,18 +112,18 @@ func (p *PostProcessor) PostProcess(ui packer.Ui, artifact packer.Artifact) (pac
return nil, false, fmt.Errorf("Error rendering gcs_object_name template: %s", err)
}
rawImageGcsPath, err := UploadToBucket(p.config.AccountFile, ui, artifact, p.config.Bucket, p.config.GCSObjectName)
rawImageGcsPath, err := UploadToBucket(client, ui, artifact, p.config.Bucket, p.config.GCSObjectName)
if err != nil {
return nil, p.config.KeepOriginalImage, err
}
gceImageArtifact, err := CreateGceImage(p.config.AccountFile, ui, p.config.ProjectId, rawImageGcsPath, p.config.ImageName, p.config.ImageDescription, p.config.ImageFamily, p.config.ImageLabels, p.config.ImageGuestOsFeatures)
gceImageArtifact, err := CreateGceImage(client, ui, p.config.ProjectId, rawImageGcsPath, p.config.ImageName, p.config.ImageDescription, p.config.ImageFamily, p.config.ImageLabels, p.config.ImageGuestOsFeatures)
if err != nil {
return nil, p.config.KeepOriginalImage, err
}
if !p.config.SkipClean {
err = DeleteFromBucket(p.config.AccountFile, ui, p.config.Bucket, p.config.GCSObjectName)
err = DeleteFromBucket(client, ui, p.config.Bucket, p.config.GCSObjectName)
if err != nil {
return nil, p.config.KeepOriginalImage, err
}
@ -128,24 +132,7 @@ func (p *PostProcessor) PostProcess(ui packer.Ui, artifact packer.Artifact) (pac
return gceImageArtifact, p.config.KeepOriginalImage, nil
}
func UploadToBucket(accountFile string, ui packer.Ui, artifact packer.Artifact, bucket string, gcsObjectName string) (string, error) {
var client *http.Client
var account googlecompute.AccountFile
err := googlecompute.ProcessAccountFile(&account, accountFile)
if err != nil {
return "", err
}
var DriverScopes = []string{"https://www.googleapis.com/auth/devstorage.full_control"}
conf := jwt.Config{
Email: account.ClientEmail,
PrivateKey: []byte(account.PrivateKey),
Scopes: DriverScopes,
TokenURL: "https://accounts.google.com/o/oauth2/token",
}
client = conf.Client(oauth2.NoContext)
func UploadToBucket(client *http.Client, ui packer.Ui, artifact packer.Artifact, bucket string, gcsObjectName string) (string, error) {
service, err := storage.New(client)
if err != nil {
return "", err
@ -162,7 +149,7 @@ func UploadToBucket(accountFile string, ui packer.Ui, artifact packer.Artifact,
}
if source == "" {
return "", fmt.Errorf("No tar.gz file found in list of articats")
return "", fmt.Errorf("No tar.gz file found in list of artifacts")
}
artifactFile, err := os.Open(source)
@ -178,28 +165,10 @@ func UploadToBucket(accountFile string, ui packer.Ui, artifact packer.Artifact,
return "", err
}
return "https://storage.googleapis.com/" + bucket + "/" + gcsObjectName, nil
return storageObject.SelfLink, nil
}
func CreateGceImage(accountFile string, ui packer.Ui, project string, rawImageURL string, imageName string, imageDescription string, imageFamily string, imageLabels map[string]string, imageGuestOsFeatures []string) (packer.Artifact, error) {
var client *http.Client
var account googlecompute.AccountFile
err := googlecompute.ProcessAccountFile(&account, accountFile)
if err != nil {
return nil, err
}
var DriverScopes = []string{"https://www.googleapis.com/auth/compute", "https://www.googleapis.com/auth/devstorage.full_control"}
conf := jwt.Config{
Email: account.ClientEmail,
PrivateKey: []byte(account.PrivateKey),
Scopes: DriverScopes,
TokenURL: "https://accounts.google.com/o/oauth2/token",
}
client = conf.Client(oauth2.NoContext)
func CreateGceImage(client *http.Client, ui packer.Ui, project string, rawImageURL string, imageName string, imageDescription string, imageFamily string, imageLabels map[string]string, imageGuestOsFeatures []string) (packer.Artifact, error) {
service, err := compute.New(client)
if err != nil {
return nil, err
@ -253,24 +222,7 @@ func CreateGceImage(accountFile string, ui packer.Ui, project string, rawImageUR
return &Artifact{paths: []string{op.TargetLink}}, nil
}
func DeleteFromBucket(accountFile string, ui packer.Ui, bucket string, gcsObjectName string) error {
var client *http.Client
var account googlecompute.AccountFile
err := googlecompute.ProcessAccountFile(&account, accountFile)
if err != nil {
return err
}
var DriverScopes = []string{"https://www.googleapis.com/auth/devstorage.full_control"}
conf := jwt.Config{
Email: account.ClientEmail,
PrivateKey: []byte(account.PrivateKey),
Scopes: DriverScopes,
TokenURL: "https://accounts.google.com/o/oauth2/token",
}
client = conf.Client(oauth2.NoContext)
func DeleteFromBucket(client *http.Client, ui packer.Ui, bucket string, gcsObjectName string) error {
service, err := storage.New(client)
if err != nil {
return err

View File

@ -10,12 +10,13 @@ type ArtifactFile struct {
}
type Artifact struct {
BuildName string `json:"name"`
BuilderType string `json:"builder_type"`
BuildTime int64 `json:"build_time"`
ArtifactFiles []ArtifactFile `json:"files"`
ArtifactId string `json:"artifact_id"`
PackerRunUUID string `json:"packer_run_uuid"`
BuildName string `json:"name"`
BuilderType string `json:"builder_type"`
BuildTime int64 `json:"build_time"`
ArtifactFiles []ArtifactFile `json:"files"`
ArtifactId string `json:"artifact_id"`
PackerRunUUID string `json:"packer_run_uuid"`
CustomData map[string]string `json:"custom_data"`
}
func (a *Artifact) BuilderId() string {

View File

@ -18,8 +18,9 @@ import (
type Config struct {
common.PackerConfig `mapstructure:",squash"`
OutputPath string `mapstructure:"output"`
StripPath bool `mapstructure:"strip_path"`
OutputPath string `mapstructure:"output"`
StripPath bool `mapstructure:"strip_path"`
CustomData map[string]string `mapstructure:"custom_data"`
ctx interpolate.Context
}
@ -75,6 +76,7 @@ func (p *PostProcessor) PostProcess(ui packer.Ui, source packer.Artifact) (packe
artifact.ArtifactFiles = append(artifact.ArtifactFiles, af)
}
artifact.ArtifactId = source.Id()
artifact.CustomData = p.config.CustomData
artifact.BuilderType = p.config.PackerBuilderType
artifact.BuildName = p.config.PackerBuildName
artifact.BuildTime = time.Now().Unix()

View File

@ -5,63 +5,109 @@
ALL_XC_ARCH="386 amd64 arm arm64 ppc64le"
ALL_XC_OS="linux darwin windows freebsd openbsd solaris"
# Exit immediately if a command fails
set -e
# Validates that a necessary tool is on the PATH
function validateToolPresence
{
local TOOLNAME=$1
which ${TOOLNAME} >/dev/null || echo "${TOOLNAME} is not on the path. Exiting..."
exit 1
}
# Validates that all used tools are present; exits when any is not found
function validatePreconditions
{
echo "==> Checking for necessary tools..."
validateToolPresence realpath
validateToolPresence dirname
validateToolPresence tr
validateToolPresence find
}
# Get the parent directory of where this script is.
SOURCE="${BASH_SOURCE[0]}"
while [ -h "$SOURCE" ] ; do SOURCE="$(readlink "$SOURCE")"; done
DIR="$( cd -P "$( dirname "$SOURCE" )/.." && pwd )"
# NOTE: I'm unsure why you don't just use realpath like below
function enterPackerSourceDir
{
echo "==> Entering Packer source dir..."
local BUILD_SCRIPT_PATH="${BASH_SOURCE[0]}"
SOURCEDIR=$(dirname $(dirname $(realpath "${BUILD_SCRIPT_PATH}")))
cd ${SOURCEDIR}
}
# Change into that directory
cd $DIR
function ensureOutputStructure {
echo "==> Ensuring output directories are present..."
mkdir -p bin/
mkdir -p pkg/
}
# Delete the old dir
echo "==> Removing old directory..."
rm -f bin/*
rm -rf pkg/*
mkdir -p bin/
function cleanOutputDirs {
echo "==> Removing old builds..."
rm -f bin/*
rm -fr pkg/*
}
# helpers for Cygwin-hosted builds
: ${OSTYPE:=`uname`}
function lowerCaseOSType {
local OS_TYPE=${OSTYPE:=`uname`}
echo "${OS_TYPE}" | tr "[:upper:]" "[:lower:]"
}
case $OSTYPE in
MINGW*|MSYS*|cygwin|CYGWIN*)
# cygwin only translates ';' to ':' on select environment variables
PATHSEP=';'
;;
*) PATHSEP=':'
esac
function convert_path() {
local flag
[ "${1:0:1}" = '-' ] && { flag="$1"; shift; }
[ -n "$1" ] || return 0
case ${OSTYPE:-`uname`} in
cygwin|CYGWIN*)
cygpath $flag -- "$1"
# Returns the OS appropriate path separator
function getPathSeparator {
# helpers for Cygwin-hosted builds
case "$(lowerCaseOSType)" in
mingw*|msys*|cygwin*)
# cygwin only translates ';' to ':' on select environment variables
echo ';'
;;
*) echo "$1"
*) echo ':'
esac
}
# XXX works in MINGW?
which go &>/dev/null || PATH+=":`convert_path "${GOROOT:?}"`/bin"
function convertPathOnCygwin() {
local flag
local somePath
if [ "${1:0:1}" = '-' ]; then
flag=$1
somePath=$2
else
somePath=$1
fi
OLDIFS="$IFS"
[ -n "${somePath}" ] || return 0
case "$(lowerCaseOSType)" in
cygwin*)
cygpath ${flag} -- "${somePath}"
;;
*) echo "${somePath}"
esac
}
enterPackerSourceDir
ensureOutputStructure
cleanOutputDirs
PATHSEP=$(getPathSeparator)
# XXX works in MINGW?
# FIXME: What if go is not in the PATH and GOROOT isn't set?
which go &>/dev/null || PATH+=":`convertPathOnCygwin "${GOROOT:?}"`/bin"
OLDIFS="${IFS}"
# make sure GOPATH is consistent - Windows binaries can't handle Cygwin-style paths
IFS="$PATHSEP"
IFS="${PATHSEP}"
for d in ${GOPATH:-$(go env GOPATH)}; do
_GOPATH+="${_GOPATH:+$PATHSEP}$(convert_path --windows "$d")"
_GOPATH+="${_GOPATH:+${PATHSEP}}$(convertPathOnCygwin --windows "${d}")"
done
GOPATH="$_GOPATH"
# locate 'gox' and traverse GOPATH if needed
which "${GOX:=gox}" &>/dev/null || {
for d in $GOPATH; do
GOX="$(convert_path --unix "$d")/bin/gox"
[ -x "$GOX" ] && break || unset GOX
for d in ${GOPATH}; do
GOX="$(convertPathOnCygwin --unix "${d}")/bin/gox"
[ -x "${GOX}" ] && break || unset GOX
done
}
IFS="$OLDIFS"
@ -86,17 +132,18 @@ ${GOX:?command not found} \
set -e
# trim GOPATH to first element
IFS="$PATHSEP"
MAIN_GOPATH=($GOPATH)
MAIN_GOPATH="$(convert_path --unix "$MAIN_GOPATH")"
IFS=$OLDIFS
IFS="${PATHSEP}"
# FIXME: How do you know that the first path of GOPATH is the main GOPATH? Or is the main GOPATH meant to be the first path in GOPATH?
MAIN_GOPATH=(${GOPATH})
MAIN_GOPATH="$(convertPathOnCygwin --unix "${MAIN_GOPATH[0]}")"
IFS="${OLDIFS}"
# Copy our OS/Arch to the bin/ directory
echo "==> Copying binaries for this platform..."
DEV_PLATFORM="./pkg/$(go env GOOS)_$(go env GOARCH)"
DEV_PLATFORM="./pkg/${XC_OS}_${XC_ARCH}"
for F in $(find ${DEV_PLATFORM} -mindepth 1 -maxdepth 1 -type f 2>/dev/null); do
cp -v ${F} bin/
cp -v ${F} ${MAIN_GOPATH}/bin/
cp -v ${F} "${MAIN_GOPATH}/bin/"
done
# Done!

View File

@ -94,7 +94,7 @@ builder.
Packer to wait for 1 minute 30 seconds before typing the boot command.
The default duration is "10s" (10 seconds).
- `configuration_version` - This allows you to set the vm version when
- `configuration_version` (string) - This allows you to set the vm version when
calling New-VM to generate the vm.
- `cpu` (number) - The number of CPUs the virtual machine should use. If

View File

@ -116,7 +116,7 @@ builder.
This setting only has an effect if using `clone_from_vm_name` and is
ignored otherwise.
- `copy_in_compare` - (bool) When cloning a vm to build from, we run a powershell
- `copy_in_compare` (boolean) - When cloning a vm to build from, we run a powershell
Compare-VM command, which, depending on your version of Windows, may need
the "Copy" flag to be set to true or false. Defaults to "false". Command:
@ -125,7 +125,7 @@ builder.
Where $copy is replaced with either true or false depending on the value of
"copy_in_compare".
- `configuration_version` - This allows you to set the vm version when
- `configuration_version` (string) - This allows you to set the vm version when
calling New-VM to generate the vm.
- `cpu` (number) - The number of CPUs the virtual machine should use. If

View File

@ -3,8 +3,8 @@ description: |
The `tencentcloud-cvm` Packer builder plugin provide the capability to build
customized images based on an existing base images.
layout: docs
page_title: Tencentcloud Image Builder
sidebar_current: 'docs-builders-tencentcloud-ecs'
page_title: 'Tencentcloud Image Builder'
sidebar_current: 'docs-builders-tencentcloud-cvm'
---
# Tencentcloud Image Builder
@ -88,16 +88,16 @@ builder.
- LOCAL_BASIC: 50
- Other: 50 ~ 1000 (need whitelist if > 50)
- `vpc_id` - Specify vpc your cvm will be launched by.
- `vpc_id` (string) - Specify vpc your cvm will be launched by.
- `vpc_name` - Specify vpc name you will create. if `vpc_id` is not set, packer will
- `vpc_name` (string) - Specify vpc name you will create. if `vpc_id` is not set, packer will
create a vpc for you named this parameter.
- `cidr_block` (boolean) - Specify cider block of the vpc you will create if `vpc_id` not set
- `subnet_id` - Specify subnet your cvm will be launched by.
- `subnet_id` (string) - Specify subnet your cvm will be launched by.
- 'subnet_name' - Specify subnet name you will create. if `subnet_id` is not set, packer will
- `subnet_name` (string) - Specify subnet name you will create. if `subnet_id` is not set, packer will
create a subnet for you named this parameter.
- `subnect_cidr_block` (boolean) - Specify cider block of the subnet you will create if
@ -106,15 +106,15 @@ builder.
- `internet_max_bandwidth_out` (number) - Max bandwidth out your cvm will be launched by(in MB).
values can be set between 1 ~ 100.
- `security_group_id` - Specify security group your cvm will be launched by.
- `security_group_id` (string) - Specify security group your cvm will be launched by.
- `security_group_name` - Specify security name you will create if `security_group_id` not set.
- `security_group_name` (string) - Specify security name you will create if `security_group_id` not set.
- `user_data` - userdata.
- `user_data` (string) - userdata.
- `user_data_file` - userdata file.
- `user_data_file` (string) - userdata file.
- `host_name` - host name.
- `host_name` (string) - host name.
## Basic Example

View File

@ -100,6 +100,9 @@ builder.
- `cpus` (number) - The number of cpus to use when building the VM.
- `cores` (number) - The number of cores per socket to use when building the VM.
This corresponds to the `cpuid.coresPerSocket` option in the .vmx file.
- `cdrom_adapter_type` (string) - The adapter type (or bus) that will be used
by the cdrom device. This is chosen by default based on the disk adapter
type. VMware tends to lean towards `ide` for the cdrom device unless

View File

@ -29,11 +29,11 @@ is optional.
- `repository` (string) - The repository of the imported image.
### Optional:
- `tag` (string) - The tag for the imported image. By default this is not
set.
### Optional:
- `changes` (array of strings) - Dockerfile instructions to add to the
commit. Example of instructions are `CMD`, `ENTRYPOINT`, `ENV`, and
`EXPOSE`. Example: `[ "USER ubuntu", "WORKDIR /app", "EXPOSE 8080" ]`

View File

@ -34,9 +34,39 @@ permissions to the GCS `paths`.
### Optional
- `account_file` (string) - The JSON file containing your account
credentials. If specified, this take precedence over `googlecompute`
builder authentication method.
- `disk_size` (number) - The size of the export instances disk, this disk
is unused for the export but a larger size increase `pd-ssd` read speed.
This defaults to `200`, which is 200GB.
- `disk_type` (string) - Type of disk used to back export instance, like
`pd-ssd` or `pd-standard`. Defaults to `pd-ssd`.
- `keep_input_artifact` (boolean) - If true, do not delete the Google Compute
Engine (GCE) image being exported.
- `machine_type` (string) - The export instance machine type. Defaults
to `"n1-highcpu-4"`.
- `network` (string) - The Google Compute network id or URL to use for the
export instance. Defaults to `"default"`. If the value is not a URL, it
will be interpolated to
`projects/((network_project_id))/global/networks/((network))`. This value
is not required if a `subnet` is specified.
- `subnetwork` (string) - The Google Compute subnetwork id or URL to use for
the export instance. Only required if the `network` has been created with
custom subnetting. Note, the region of the subnetwork must match the
`zone` in which the VM is launched. If the value is not a URL,
it will be interpolated to
`projects/((network_project_id))/regions/((region))/subnetworks/((subnetwork))`
- `zone` (string) - The zone in which to launch the export instance. Defaults
to `googlecompute` builder zone. Example: `"us-central1-a"`
## Basic Example
The following example builds a GCE image in the project, `my-project`, with an

View File

@ -38,11 +38,12 @@ post-processors such as Docker and Artifice.
to `packer-manifest.json`.
- `strip_path` (boolean) Write only filename without the path to the manifest
file. This defaults to false.
- `custom_data` (map of strings) Arbitrary data to add to the manifest.
### Example Configuration
You can simply add `{"type":"manifest"}` to your post-processor section. Below
is a more verbose example:
is a more complete example:
``` json
{
@ -50,7 +51,10 @@ is a more verbose example:
{
"type": "manifest",
"output": "manifest.json",
"strip_path": true
"strip_path": true,
"custom_data": {
"my_custom_data": "example"
}
}
]
}
@ -72,7 +76,10 @@ An example manifest file looks like:
}
],
"artifact_id": "Container",
"packer_run_uuid": "6d5d3185-fa95-44e1-8775-9e64fe2e2d8f"
"packer_run_uuid": "6d5d3185-fa95-44e1-8775-9e64fe2e2d8f",
"custom_data": {
"my_custom_data": "example"
}
}
],
"last_run_uuid": "6d5d3185-fa95-44e1-8775-9e64fe2e2d8f"
@ -114,7 +121,10 @@ The above manifest was generated with this packer.json:
{
"type": "manifest",
"output": "manifest.json",
"strip_path": true
"strip_path": true,
"custom_data": {
"my_custom_data": "example"
}
}
]
}

View File

@ -162,6 +162,9 @@
<li<%= sidebar_current("docs-builders-scaleway") %>>
<a href="/docs/builders/scaleway.html">Scaleway</a>
</li>
<li<%= sidebar_current("docs-builders-tencentcloud-cvm") %>>
<a href="/docs/builders/tencentcloud-cvm.html">Tencent Cloud</a>
</li>
<li<%= sidebar_current("docs-builders-triton") %>>
<a href="/docs/builders/triton.html">Triton</a>
</li>