Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
P
packer
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Analytics
Analytics
CI / CD
Repository
Value Stream
Wiki
Wiki
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Kristopher Ruzic
packer
Commits
4f150dcc
Commit
4f150dcc
authored
Dec 13, 2013
by
Mitchell Hashimoto
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #715 from mitchellh/f-gce-builder
Google Compute Builder
parents
9307d8a8
bae9c4d2
Changes
35
Hide whitespace changes
Inline
Side-by-side
Showing
35 changed files
with
2410 additions
and
1 deletion
+2410
-1
builder/googlecompute/artifact.go
builder/googlecompute/artifact.go
+39
-0
builder/googlecompute/artifact_test.go
builder/googlecompute/artifact_test.go
+10
-0
builder/googlecompute/builder.go
builder/googlecompute/builder.go
+100
-0
builder/googlecompute/builder_test.go
builder/googlecompute/builder_test.go
+1
-0
builder/googlecompute/client_secrets.go
builder/googlecompute/client_secrets.go
+32
-0
builder/googlecompute/client_secrets_test.go
builder/googlecompute/client_secrets_test.go
+31
-0
builder/googlecompute/config.go
builder/googlecompute/config.go
+187
-0
builder/googlecompute/config_test.go
builder/googlecompute/config_test.go
+176
-0
builder/googlecompute/driver.go
builder/googlecompute/driver.go
+35
-0
builder/googlecompute/driver_gce.go
builder/googlecompute/driver_gce.go
+309
-0
builder/googlecompute/driver_mock.go
builder/googlecompute/driver_mock.go
+108
-0
builder/googlecompute/private_key.go
builder/googlecompute/private_key.go
+43
-0
builder/googlecompute/private_key_test.go
builder/googlecompute/private_key_test.go
+78
-0
builder/googlecompute/ssh.go
builder/googlecompute/ssh.go
+34
-0
builder/googlecompute/step_create_image.go
builder/googlecompute/step_create_image.go
+52
-0
builder/googlecompute/step_create_image_test.go
builder/googlecompute/step_create_image_test.go
+96
-0
builder/googlecompute/step_create_instance.go
builder/googlecompute/step_create_instance.go
+95
-0
builder/googlecompute/step_create_instance_test.go
builder/googlecompute/step_create_instance_test.go
+128
-0
builder/googlecompute/step_create_ssh_key.go
builder/googlecompute/step_create_ssh_key.go
+51
-0
builder/googlecompute/step_create_ssh_key_test.go
builder/googlecompute/step_create_ssh_key_test.go
+29
-0
builder/googlecompute/step_instance_info.go
builder/googlecompute/step_instance_info.go
+53
-0
builder/googlecompute/step_instance_info_test.go
builder/googlecompute/step_instance_info_test.go
+134
-0
builder/googlecompute/step_register_image.go
builder/googlecompute/step_register_image.go
+46
-0
builder/googlecompute/step_register_image_test.go
builder/googlecompute/step_register_image_test.go
+100
-0
builder/googlecompute/step_test.go
builder/googlecompute/step_test.go
+20
-0
builder/googlecompute/step_update_gsutil.go
builder/googlecompute/step_update_gsutil.go
+52
-0
builder/googlecompute/step_update_gsutil_test.go
builder/googlecompute/step_update_gsutil_test.go
+85
-0
builder/googlecompute/step_upload_image.go
builder/googlecompute/step_upload_image.go
+45
-0
builder/googlecompute/step_upload_image_test.go
builder/googlecompute/step_upload_image_test.go
+88
-0
config.go
config.go
+1
-0
plugin/builder-googlecompute/main.go
plugin/builder-googlecompute/main.go
+15
-0
plugin/builder-googlecompute/main_test.go
plugin/builder-googlecompute/main_test.go
+1
-0
website/source/docs/builders/googlecompute.markdown
website/source/docs/builders/googlecompute.markdown
+127
-0
website/source/layouts/docs.erb
website/source/layouts/docs.erb
+1
-0
website/source/stylesheets/_components.scss
website/source/stylesheets/_components.scss
+8
-1
No files found.
builder/googlecompute/artifact.go
0 → 100644
View file @
4f150dcc
package
googlecompute
import
(
"fmt"
"log"
)
// Artifact represents a GCE image as the result of a Packer build.
type
Artifact
struct
{
imageName
string
driver
Driver
}
// BuilderId returns the builder Id.
func
(
*
Artifact
)
BuilderId
()
string
{
return
BuilderId
}
// Destroy destroys the GCE image represented by the artifact.
func
(
a
*
Artifact
)
Destroy
()
error
{
log
.
Printf
(
"Destroying image: %s"
,
a
.
imageName
)
errCh
:=
a
.
driver
.
DeleteImage
(
a
.
imageName
)
return
<-
errCh
}
// Files returns the files represented by the artifact.
func
(
*
Artifact
)
Files
()
[]
string
{
return
nil
}
// Id returns the GCE image name.
func
(
a
*
Artifact
)
Id
()
string
{
return
a
.
imageName
}
// String returns the string representation of the artifact.
func
(
a
*
Artifact
)
String
()
string
{
return
fmt
.
Sprintf
(
"A disk image was created: %v"
,
a
.
imageName
)
}
builder/googlecompute/artifact_test.go
0 → 100644
View file @
4f150dcc
package
googlecompute
import
(
"github.com/mitchellh/packer/packer"
"testing"
)
func
TestArtifact_impl
(
t
*
testing
.
T
)
{
var
_
packer
.
Artifact
=
new
(
Artifact
)
}
builder/googlecompute/builder.go
0 → 100644
View file @
4f150dcc
// The googlecompute package contains a packer.Builder implementation that
// builds images for Google Compute Engine.
package
googlecompute
import
(
"log"
"time"
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/common"
"github.com/mitchellh/packer/packer"
)
// The unique ID for this builder.
const
BuilderId
=
"packer.googlecompute"
// Builder represents a Packer Builder.
type
Builder
struct
{
config
*
Config
runner
multistep
.
Runner
}
// Prepare processes the build configuration parameters.
func
(
b
*
Builder
)
Prepare
(
raws
...
interface
{})
([]
string
,
error
)
{
c
,
warnings
,
errs
:=
NewConfig
(
raws
...
)
if
errs
!=
nil
{
return
warnings
,
errs
}
b
.
config
=
c
return
warnings
,
nil
}
// Run executes a googlecompute Packer build and returns a packer.Artifact
// representing a GCE machine image.
func
(
b
*
Builder
)
Run
(
ui
packer
.
Ui
,
hook
packer
.
Hook
,
cache
packer
.
Cache
)
(
packer
.
Artifact
,
error
)
{
driver
,
err
:=
NewDriverGCE
(
ui
,
b
.
config
.
ProjectId
,
b
.
config
.
clientSecrets
,
b
.
config
.
privateKeyBytes
)
if
err
!=
nil
{
return
nil
,
err
}
// Set up the state.
state
:=
new
(
multistep
.
BasicStateBag
)
state
.
Put
(
"config"
,
b
.
config
)
state
.
Put
(
"driver"
,
driver
)
state
.
Put
(
"hook"
,
hook
)
state
.
Put
(
"ui"
,
ui
)
// Build the steps.
steps
:=
[]
multistep
.
Step
{
new
(
StepCreateSSHKey
),
new
(
StepCreateInstance
),
new
(
StepInstanceInfo
),
&
common
.
StepConnectSSH
{
SSHAddress
:
sshAddress
,
SSHConfig
:
sshConfig
,
SSHWaitTimeout
:
5
*
time
.
Minute
,
},
new
(
common
.
StepProvision
),
new
(
StepUpdateGsutil
),
new
(
StepCreateImage
),
new
(
StepUploadImage
),
new
(
StepRegisterImage
),
}
// Run the steps.
if
b
.
config
.
PackerDebug
{
b
.
runner
=
&
multistep
.
DebugRunner
{
Steps
:
steps
,
PauseFn
:
common
.
MultistepDebugFn
(
ui
),
}
}
else
{
b
.
runner
=
&
multistep
.
BasicRunner
{
Steps
:
steps
}
}
b
.
runner
.
Run
(
state
)
// Report any errors.
if
rawErr
,
ok
:=
state
.
GetOk
(
"error"
);
ok
{
return
nil
,
rawErr
.
(
error
)
}
if
_
,
ok
:=
state
.
GetOk
(
"image_name"
);
!
ok
{
log
.
Println
(
"Failed to find image_name in state. Bug?"
)
return
nil
,
nil
}
artifact
:=
&
Artifact
{
imageName
:
state
.
Get
(
"image_name"
)
.
(
string
),
driver
:
driver
,
}
return
artifact
,
nil
}
// Cancel.
func
(
b
*
Builder
)
Cancel
()
{
if
b
.
runner
!=
nil
{
log
.
Println
(
"Cancelling the step runner..."
)
b
.
runner
.
Cancel
()
}
}
builder/googlecompute/builder_test.go
0 → 100644
View file @
4f150dcc
package
googlecompute
builder/googlecompute/client_secrets.go
0 → 100644
View file @
4f150dcc
package
googlecompute
import
(
"encoding/json"
"io/ioutil"
)
// clientSecrets represents the client secrets of a GCE service account.
type
clientSecrets
struct
{
Web
struct
{
AuthURI
string
`json:"auth_uri"`
ClientEmail
string
`json:"client_email"`
ClientId
string
`json:"client_id"`
TokenURI
string
`json:"token_uri"`
}
}
// loadClientSecrets loads the GCE client secrets file identified by path.
func
loadClientSecrets
(
path
string
)
(
*
clientSecrets
,
error
)
{
var
cs
*
clientSecrets
secretBytes
,
err
:=
ioutil
.
ReadFile
(
path
)
if
err
!=
nil
{
return
nil
,
err
}
err
=
json
.
Unmarshal
(
secretBytes
,
&
cs
)
if
err
!=
nil
{
return
nil
,
err
}
return
cs
,
nil
}
builder/googlecompute/client_secrets_test.go
0 → 100644
View file @
4f150dcc
package
googlecompute
import
(
"io/ioutil"
"testing"
)
func
testClientSecretsFile
(
t
*
testing
.
T
)
string
{
tf
,
err
:=
ioutil
.
TempFile
(
""
,
"packer"
)
if
err
!=
nil
{
t
.
Fatalf
(
"err: %s"
,
err
)
}
defer
tf
.
Close
()
if
_
,
err
:=
tf
.
Write
([]
byte
(
testClientSecretsContent
));
err
!=
nil
{
t
.
Fatalf
(
"err: %s"
,
err
)
}
return
tf
.
Name
()
}
func
TestLoadClientSecrets
(
t
*
testing
.
T
)
{
_
,
err
:=
loadClientSecrets
(
testClientSecretsFile
(
t
))
if
err
!=
nil
{
t
.
Fatalf
(
"err: %s"
,
err
)
}
}
// This is just some dummy data that doesn't actually work (it was revoked
// a long time ago).
const
testClientSecretsContent
=
`{"web":{"auth_uri":"https://accounts.google.com/o/oauth2/auth","token_uri":"https://accounts.google.com/o/oauth2/token","client_email":"774313886706-eorlsj0r4eqkh5e7nvea5fuf59ifr873@developer.gserviceaccount.com","client_x509_cert_url":"https://www.googleapis.com/robot/v1/metadata/x509/774313886706-eorlsj0r4eqkh5e7nvea5fuf59ifr873@developer.gserviceaccount.com","client_id":"774313886706-eorlsj0r4eqkh5e7nvea5fuf59ifr873.apps.googleusercontent.com","auth_provider_x509_cert_url":"https://www.googleapis.com/oauth2/v1/certs"}}`
builder/googlecompute/config.go
0 → 100644
View file @
4f150dcc
package
googlecompute
import
(
"errors"
"fmt"
"time"
"github.com/mitchellh/packer/common"
"github.com/mitchellh/packer/packer"
)
// Config is the configuration structure for the GCE builder. It stores
// both the publicly settable state as well as the privately generated
// state of the config object.
type
Config
struct
{
common
.
PackerConfig
`mapstructure:",squash"`
BucketName
string
`mapstructure:"bucket_name"`
ClientSecretsFile
string
`mapstructure:"client_secrets_file"`
ImageName
string
`mapstructure:"image_name"`
ImageDescription
string
`mapstructure:"image_description"`
MachineType
string
`mapstructure:"machine_type"`
Metadata
map
[
string
]
string
`mapstructure:"metadata"`
Network
string
`mapstructure:"network"`
Passphrase
string
`mapstructure:"passphrase"`
PrivateKeyFile
string
`mapstructure:"private_key_file"`
ProjectId
string
`mapstructure:"project_id"`
SourceImage
string
`mapstructure:"source_image"`
SSHUsername
string
`mapstructure:"ssh_username"`
SSHPort
uint
`mapstructure:"ssh_port"`
RawSSHTimeout
string
`mapstructure:"ssh_timeout"`
RawStateTimeout
string
`mapstructure:"state_timeout"`
Tags
[]
string
`mapstructure:"tags"`
Zone
string
`mapstructure:"zone"`
clientSecrets
*
clientSecrets
instanceName
string
privateKeyBytes
[]
byte
sshTimeout
time
.
Duration
stateTimeout
time
.
Duration
tpl
*
packer
.
ConfigTemplate
}
func
NewConfig
(
raws
...
interface
{})
(
*
Config
,
[]
string
,
error
)
{
c
:=
new
(
Config
)
md
,
err
:=
common
.
DecodeConfig
(
c
,
raws
...
)
if
err
!=
nil
{
return
nil
,
nil
,
err
}
c
.
tpl
,
err
=
packer
.
NewConfigTemplate
()
if
err
!=
nil
{
return
nil
,
nil
,
err
}
c
.
tpl
.
UserVars
=
c
.
PackerUserVars
// Prepare the errors
errs
:=
common
.
CheckUnusedConfig
(
md
)
// Set defaults.
if
c
.
Network
==
""
{
c
.
Network
=
"default"
}
if
c
.
ImageDescription
==
""
{
c
.
ImageDescription
=
"Created by Packer"
}
if
c
.
ImageName
==
""
{
c
.
ImageName
=
"packer-{{timestamp}}"
}
if
c
.
MachineType
==
""
{
c
.
MachineType
=
"n1-standard-1"
}
if
c
.
RawSSHTimeout
==
""
{
c
.
RawSSHTimeout
=
"5m"
}
if
c
.
RawStateTimeout
==
""
{
c
.
RawStateTimeout
=
"5m"
}
if
c
.
SSHUsername
==
""
{
c
.
SSHUsername
=
"root"
}
if
c
.
SSHPort
==
0
{
c
.
SSHPort
=
22
}
// Process Templates
templates
:=
map
[
string
]
*
string
{
"bucket_name"
:
&
c
.
BucketName
,
"client_secrets_file"
:
&
c
.
ClientSecretsFile
,
"image_name"
:
&
c
.
ImageName
,
"image_description"
:
&
c
.
ImageDescription
,
"machine_type"
:
&
c
.
MachineType
,
"network"
:
&
c
.
Network
,
"passphrase"
:
&
c
.
Passphrase
,
"private_key_file"
:
&
c
.
PrivateKeyFile
,
"project_id"
:
&
c
.
ProjectId
,
"source_image"
:
&
c
.
SourceImage
,
"ssh_username"
:
&
c
.
SSHUsername
,
"ssh_timeout"
:
&
c
.
RawSSHTimeout
,
"state_timeout"
:
&
c
.
RawStateTimeout
,
"zone"
:
&
c
.
Zone
,
}
for
n
,
ptr
:=
range
templates
{
var
err
error
*
ptr
,
err
=
c
.
tpl
.
Process
(
*
ptr
,
nil
)
if
err
!=
nil
{
errs
=
packer
.
MultiErrorAppend
(
errs
,
fmt
.
Errorf
(
"Error processing %s: %s"
,
n
,
err
))
}
}
// Process required parameters.
if
c
.
BucketName
==
""
{
errs
=
packer
.
MultiErrorAppend
(
errs
,
errors
.
New
(
"a bucket_name must be specified"
))
}
if
c
.
ClientSecretsFile
==
""
{
errs
=
packer
.
MultiErrorAppend
(
errs
,
errors
.
New
(
"a client_secrets_file must be specified"
))
}
if
c
.
PrivateKeyFile
==
""
{
errs
=
packer
.
MultiErrorAppend
(
errs
,
errors
.
New
(
"a private_key_file must be specified"
))
}
if
c
.
ProjectId
==
""
{
errs
=
packer
.
MultiErrorAppend
(
errs
,
errors
.
New
(
"a project_id must be specified"
))
}
if
c
.
SourceImage
==
""
{
errs
=
packer
.
MultiErrorAppend
(
errs
,
errors
.
New
(
"a source_image must be specified"
))
}
if
c
.
Zone
==
""
{
errs
=
packer
.
MultiErrorAppend
(
errs
,
errors
.
New
(
"a zone must be specified"
))
}
// Process timeout settings.
sshTimeout
,
err
:=
time
.
ParseDuration
(
c
.
RawSSHTimeout
)
if
err
!=
nil
{
errs
=
packer
.
MultiErrorAppend
(
errs
,
fmt
.
Errorf
(
"Failed parsing ssh_timeout: %s"
,
err
))
}
c
.
sshTimeout
=
sshTimeout
stateTimeout
,
err
:=
time
.
ParseDuration
(
c
.
RawStateTimeout
)
if
err
!=
nil
{
errs
=
packer
.
MultiErrorAppend
(
errs
,
fmt
.
Errorf
(
"Failed parsing state_timeout: %s"
,
err
))
}
c
.
stateTimeout
=
stateTimeout
// Load the client secrets file.
cs
,
err
:=
loadClientSecrets
(
c
.
ClientSecretsFile
)
if
err
!=
nil
{
errs
=
packer
.
MultiErrorAppend
(
errs
,
fmt
.
Errorf
(
"Failed parsing client secrets file: %s"
,
err
))
}
c
.
clientSecrets
=
cs
// Load the private key.
c
.
privateKeyBytes
,
err
=
processPrivateKeyFile
(
c
.
PrivateKeyFile
,
c
.
Passphrase
)
if
err
!=
nil
{
errs
=
packer
.
MultiErrorAppend
(
errs
,
fmt
.
Errorf
(
"Failed loading private key file: %s"
,
err
))
}
// Check for any errors.
if
errs
!=
nil
&&
len
(
errs
.
Errors
)
>
0
{
return
nil
,
nil
,
errs
}
return
c
,
nil
,
nil
}
builder/googlecompute/config_test.go
0 → 100644
View file @
4f150dcc
package
googlecompute
import
(
"testing"
)
func
testConfig
(
t
*
testing
.
T
)
map
[
string
]
interface
{}
{
return
map
[
string
]
interface
{}{
"bucket_name"
:
"foo"
,
"client_secrets_file"
:
testClientSecretsFile
(
t
),
"private_key_file"
:
testPrivateKeyFile
(
t
),
"project_id"
:
"hashicorp"
,
"source_image"
:
"foo"
,
"zone"
:
"us-east-1a"
,
}
}
func
testConfigStruct
(
t
*
testing
.
T
)
*
Config
{
c
,
warns
,
errs
:=
NewConfig
(
testConfig
(
t
))
if
len
(
warns
)
>
0
{
t
.
Fatalf
(
"bad: %#v"
,
len
(
warns
))
}
if
errs
!=
nil
{
t
.
Fatalf
(
"bad: %#v"
,
errs
)
}
return
c
}
func
testConfigErr
(
t
*
testing
.
T
,
warns
[]
string
,
err
error
,
extra
string
)
{
if
len
(
warns
)
>
0
{
t
.
Fatalf
(
"bad: %#v"
,
warns
)
}
if
err
==
nil
{
t
.
Fatalf
(
"should error: %s"
,
extra
)
}
}
func
testConfigOk
(
t
*
testing
.
T
,
warns
[]
string
,
err
error
)
{
if
len
(
warns
)
>
0
{
t
.
Fatalf
(
"bad: %#v"
,
warns
)
}
if
err
!=
nil
{
t
.
Fatalf
(
"bad: %s"
,
err
)
}
}
func
TestConfigPrepare
(
t
*
testing
.
T
)
{
cases
:=
[]
struct
{
Key
string
Value
interface
{}
Err
bool
}{
{
"unknown_key"
,
"bad"
,
true
,
},
{
"bucket_name"
,
nil
,
true
,
},
{
"bucket_name"
,
"good"
,
false
,
},
{
"client_secrets_file"
,
nil
,
true
,
},
{
"client_secrets_file"
,
testClientSecretsFile
(
t
),
false
,
},
{
"client_secrets_file"
,
"/tmp/i/should/not/exist"
,
true
,
},
{
"private_key_file"
,
nil
,
true
,
},
{
"private_key_file"
,
testPrivateKeyFile
(
t
),
false
,
},
{
"private_key_file"
,
"/tmp/i/should/not/exist"
,
true
,
},
{
"project_id"
,
nil
,
true
,
},
{
"project_id"
,
"foo"
,
false
,
},
{
"source_image"
,
nil
,
true
,
},
{
"source_image"
,
"foo"
,
false
,
},
{
"zone"
,
nil
,
true
,
},
{
"zone"
,
"foo"
,
false
,
},
{
"ssh_timeout"
,
"SO BAD"
,
true
,
},
{
"ssh_timeout"
,
"5s"
,
false
,
},
{
"state_timeout"
,
"SO BAD"
,
true
,
},
{
"state_timeout"
,
"5s"
,
false
,
},
}
for
_
,
tc
:=
range
cases
{
raw
:=
testConfig
(
t
)
if
tc
.
Value
==
nil
{
delete
(
raw
,
tc
.
Key
)
}
else
{
raw
[
tc
.
Key
]
=
tc
.
Value
}
_
,
warns
,
errs
:=
NewConfig
(
raw
)
if
tc
.
Err
{
testConfigErr
(
t
,
warns
,
errs
,
tc
.
Key
)
}
else
{
testConfigOk
(
t
,
warns
,
errs
)
}
}
}
builder/googlecompute/driver.go
0 → 100644
View file @
4f150dcc
package
googlecompute
// Driver is the interface that has to be implemented to communicate
// with GCE. The Driver interface exists mostly to allow a mock implementation
// to be used to test the steps.
type
Driver
interface
{
// CreateImage creates an image with the given URL in Google Storage.
CreateImage
(
name
,
description
,
url
string
)
<-
chan
error
// DeleteImage deletes the image with the given name.
DeleteImage
(
name
string
)
<-
chan
error
// DeleteInstance deletes the given instance.
DeleteInstance
(
zone
,
name
string
)
(
<-
chan
error
,
error
)
// GetNatIP gets the NAT IP address for the instance.
GetNatIP
(
zone
,
name
string
)
(
string
,
error
)
// RunInstance takes the given config and launches an instance.
RunInstance
(
*
InstanceConfig
)
(
<-
chan
error
,
error
)
// WaitForInstance waits for an instance to reach the given state.
WaitForInstance
(
state
,
zone
,
name
string
)
<-
chan
error
}
type
InstanceConfig
struct
{
Description
string
Image
string
MachineType
string
Metadata
map
[
string
]
string
Name
string
Network
string
Tags
[]
string
Zone
string
}
builder/googlecompute/driver_gce.go
0 → 100644
View file @
4f150dcc
package
googlecompute
import
(
"fmt"
"log"
"net/http"
"time"
"code.google.com/p/goauth2/oauth"
"code.google.com/p/goauth2/oauth/jwt"
"code.google.com/p/google-api-go-client/compute/v1beta16"
"github.com/mitchellh/packer/packer"
)
// driverGCE is a Driver implementation that actually talks to GCE.
// Create an instance using NewDriverGCE.
type
driverGCE
struct
{
projectId
string
service
*
compute
.
Service
ui
packer
.
Ui
}
const
DriverScopes
string
=
"https://www.googleapis.com/auth/compute "
+
"https://www.googleapis.com/auth/devstorage.full_control"
func
NewDriverGCE
(
ui
packer
.
Ui
,
projectId
string
,
c
*
clientSecrets
,
key
[]
byte
)
(
Driver
,
error
)
{
log
.
Printf
(
"[INFO] Requesting token..."
)
log
.
Printf
(
"[INFO] -- Email: %s"
,
c
.
Web
.
ClientEmail
)
log
.
Printf
(
"[INFO] -- Scopes: %s"
,
DriverScopes
)
log
.
Printf
(
"[INFO] -- Private Key Length: %d"
,
len
(
key
))
log
.
Printf
(
"[INFO] -- Token URL: %s"
,
c
.
Web
.
TokenURI
)
jwtTok
:=
jwt
.
NewToken
(
c
.
Web
.
ClientEmail
,
DriverScopes
,
key
)
jwtTok
.
ClaimSet
.
Aud
=
c
.
Web
.
TokenURI
token
,
err
:=
jwtTok
.
Assert
(
new
(
http
.
Client
))
if
err
!=
nil
{
return
nil
,
err
}
transport
:=
&
oauth
.
Transport
{
Config
:
&
oauth
.
Config
{
ClientId
:
c
.
Web
.
ClientId
,
Scope
:
DriverScopes
,
TokenURL
:
c
.
Web
.
TokenURI
,
AuthURL
:
c
.
Web
.
AuthURI
,
},
Token
:
token
,
}
log
.
Printf
(
"[INFO] Instantiating client..."
)
service
,
err
:=
compute
.
New
(
transport
.
Client
())
if
err
!=
nil
{
return
nil
,
err
}
return
&
driverGCE
{
projectId
:
projectId
,
service
:
service
,
ui
:
ui
,
},
nil
}
func
(
d
*
driverGCE
)
CreateImage
(
name
,
description
,
url
string
)
<-
chan
error
{
image
:=
&
compute
.
Image
{
Description
:
description
,
Name
:
name
,
RawDisk
:
&
compute
.
ImageRawDisk
{
ContainerType
:
"TAR"
,
Source
:
url
,
},
SourceType
:
"RAW"
,
}
errCh
:=
make
(
chan
error
,
1
)
op
,
err
:=
d
.
service
.
Images
.
Insert
(
d
.
projectId
,
image
)
.
Do
()
if
err
!=
nil
{
errCh
<-
err
}
else
{
go
waitForState
(
errCh
,
"DONE"
,
d
.
refreshGlobalOp
(
op
))
}
return
errCh
}
func
(
d
*
driverGCE
)
DeleteImage
(
name
string
)
<-
chan
error
{
errCh
:=
make
(
chan
error
,
1
)
op
,
err
:=
d
.
service
.
Images
.
Delete
(
d
.
projectId
,
name
)
.
Do
()
if
err
!=
nil
{
errCh
<-
err
}
else
{
go
waitForState
(
errCh
,
"DONE"
,
d
.
refreshGlobalOp
(
op
))
}
return
errCh
}
func
(
d
*
driverGCE
)
DeleteInstance
(
zone
,
name
string
)
(
<-
chan
error
,
error
)
{
op
,
err
:=
d
.
service
.
Instances
.
Delete
(
d
.
projectId
,
zone
,
name
)
.
Do
()
if
err
!=
nil
{
return
nil
,
err
}
errCh
:=
make
(
chan
error
,
1
)
go
waitForState
(
errCh
,
"DONE"
,
d
.
refreshZoneOp
(
zone
,
op
))
return
errCh
,
nil
}
func
(
d
*
driverGCE
)
GetNatIP
(
zone
,
name
string
)
(
string
,
error
)
{
instance
,
err
:=
d
.
service
.
Instances
.
Get
(
d
.
projectId
,
zone
,
name
)
.
Do
()
if
err
!=
nil
{
return
""
,
err
}
for
_
,
ni
:=
range
instance
.
NetworkInterfaces
{
if
ni
.
AccessConfigs
==
nil
{
continue
}
for
_
,
ac
:=
range
ni
.
AccessConfigs
{
if
ac
.
NatIP
!=
""
{
return
ac
.
NatIP
,
nil
}
}
}
return
""
,
nil
}
func
(
d
*
driverGCE
)
RunInstance
(
c
*
InstanceConfig
)
(
<-
chan
error
,
error
)
{
// Get the zone
d
.
ui
.
Message
(
fmt
.
Sprintf
(
"Loading zone: %s"
,
c
.
Zone
))
zone
,
err
:=
d
.
service
.
Zones
.
Get
(
d
.
projectId
,
c
.
Zone
)
.
Do
()
if
err
!=
nil
{
return
nil
,
err
}
// Get the image
d
.
ui
.
Message
(
fmt
.
Sprintf
(
"Loading image: %s"
,
c
.
Image
))
image
,
err
:=
d
.
getImage
(
c
.
Image
)
if
err
!=
nil
{
return
nil
,
err
}
// Get the machine type
d
.
ui
.
Message
(
fmt
.
Sprintf
(
"Loading machine type: %s"
,
c
.
MachineType
))
machineType
,
err
:=
d
.
service
.
MachineTypes
.
Get
(
d
.
projectId
,
zone
.
Name
,
c
.
MachineType
)
.
Do
()
if
err
!=
nil
{
return
nil
,
err
}
// TODO(mitchellh): deprecation warnings
// Get the network
d
.
ui
.
Message
(
fmt
.
Sprintf
(
"Loading network: %s"
,
c
.
Network
))
network
,
err
:=
d
.
service
.
Networks
.
Get
(
d
.
projectId
,
c
.
Network
)
.
Do
()
if
err
!=
nil
{
return
nil
,
err
}
// Build up the metadata
metadata
:=
make
([]
*
compute
.
MetadataItems
,
len
(
c
.
Metadata
))
for
k
,
v
:=
range
c
.
Metadata
{
metadata
=
append
(
metadata
,
&
compute
.
MetadataItems
{
Key
:
k
,
Value
:
v
,
})
}
// Create the instance information
instance
:=
compute
.
Instance
{
Description
:
c
.
Description
,
Image
:
image
.
SelfLink
,
MachineType
:
machineType
.
SelfLink
,
Metadata
:
&
compute
.
Metadata
{
Items
:
metadata
,
},
Name
:
c
.
Name
,
NetworkInterfaces
:
[]
*
compute
.
NetworkInterface
{
&
compute
.
NetworkInterface
{
AccessConfigs
:
[]
*
compute
.
AccessConfig
{
&
compute
.
AccessConfig
{
Name
:
"AccessConfig created by Packer"
,
Type
:
"ONE_TO_ONE_NAT"
,
},
},
Network
:
network
.
SelfLink
,
},
},
ServiceAccounts
:
[]
*
compute
.
ServiceAccount
{
&
compute
.
ServiceAccount
{
Email
:
"default"
,
Scopes
:
[]
string
{
"https://www.googleapis.com/auth/userinfo.email"
,
"https://www.googleapis.com/auth/compute"
,
"https://www.googleapis.com/auth/devstorage.full_control"
,
},
},
},
Tags
:
&
compute
.
Tags
{
Items
:
c
.
Tags
,
},
}
d
.
ui
.
Message
(
"Requesting instance creation..."
)
op
,
err
:=
d
.
service
.
Instances
.
Insert
(
d
.
projectId
,
zone
.
Name
,
&
instance
)
.
Do
()
if
err
!=
nil
{
return
nil
,
err
}
errCh
:=
make
(
chan
error
,
1
)
go
waitForState
(
errCh
,
"DONE"
,
d
.
refreshZoneOp
(
zone
.
Name
,
op
))
return
errCh
,
nil
}
func
(
d
*
driverGCE
)
WaitForInstance
(
state
,
zone
,
name
string
)
<-
chan
error
{
errCh
:=
make
(
chan
error
,
1
)
go
waitForState
(
errCh
,
state
,
d
.
refreshInstanceState
(
zone
,
name
))
return
errCh
}
func
(
d
*
driverGCE
)
getImage
(
name
string
)
(
image
*
compute
.
Image
,
err
error
)
{
projects
:=
[]
string
{
d
.
projectId
,
"debian-cloud"
,
"centos-cloud"
}
for
_
,
project
:=
range
projects
{
image
,
err
=
d
.
service
.
Images
.
Get
(
project
,
name
)
.
Do
()
if
err
==
nil
&&
image
!=
nil
&&
image
.
SelfLink
!=
""
{
return
}
image
=
nil
}
if
err
==
nil
{
err
=
fmt
.
Errorf
(
"Image could not be found: %s"
,
name
)
}
return
}
func
(
d
*
driverGCE
)
refreshInstanceState
(
zone
,
name
string
)
stateRefreshFunc
{
return
func
()
(
string
,
error
)
{
instance
,
err
:=
d
.
service
.
Instances
.
Get
(
d
.
projectId
,
zone
,
name
)
.
Do
()
if
err
!=
nil
{
return
""
,
err
}
return
instance
.
Status
,
nil
}
}
func
(
d
*
driverGCE
)
refreshGlobalOp
(
op
*
compute
.
Operation
)
stateRefreshFunc
{
return
func
()
(
string
,
error
)
{
newOp
,
err
:=
d
.
service
.
GlobalOperations
.
Get
(
d
.
projectId
,
op
.
Name
)
.
Do
()
if
err
!=
nil
{
return
""
,
err
}
// If the op is done, check for errors
err
=
nil
if
newOp
.
Status
==
"DONE"
{
if
newOp
.
Error
!=
nil
{
for
_
,
e
:=
range
newOp
.
Error
.
Errors
{
err
=
packer
.
MultiErrorAppend
(
err
,
fmt
.
Errorf
(
e
.
Message
))
}
}
}
return
newOp
.
Status
,
err
}
}
func
(
d
*
driverGCE
)
refreshZoneOp
(
zone
string
,
op
*
compute
.
Operation
)
stateRefreshFunc
{
return
func
()
(
string
,
error
)
{
newOp
,
err
:=
d
.
service
.
ZoneOperations
.
Get
(
d
.
projectId
,
zone
,
op
.
Name
)
.
Do
()
if
err
!=
nil
{
return
""
,
err
}
// If the op is done, check for errors
err
=
nil
if
newOp
.
Status
==
"DONE"
{
if
newOp
.
Error
!=
nil
{
for
_
,
e
:=
range
newOp
.
Error
.
Errors
{
err
=
packer
.
MultiErrorAppend
(
err
,
fmt
.
Errorf
(
e
.
Message
))
}
}
}
return
newOp
.
Status
,
err
}
}
// stateRefreshFunc is used to refresh the state of a thing and is
// used in conjunction with waitForState.
type
stateRefreshFunc
func
()
(
string
,
error
)
// waitForState will spin in a loop forever waiting for state to
// reach a certain target.
func
waitForState
(
errCh
chan
<-
error
,
target
string
,
refresh
stateRefreshFunc
)
{
for
{
state
,
err
:=
refresh
()
if
err
!=
nil
{
errCh
<-
err
return
}
if
state
==
target
{
errCh
<-
nil
return
}
time
.
Sleep
(
2
*
time
.
Second
)
}
}
builder/googlecompute/driver_mock.go
0 → 100644
View file @
4f150dcc
package
googlecompute
// DriverMock is a Driver implementation that is a mocked out so that
// it can be used for tests.
type
DriverMock
struct
{
CreateImageName
string
CreateImageDesc
string
CreateImageURL
string
CreateImageErrCh
<-
chan
error
DeleteImageName
string
DeleteImageErrCh
<-
chan
error
DeleteInstanceZone
string
DeleteInstanceName
string
DeleteInstanceErrCh
<-
chan
error
DeleteInstanceErr
error
GetNatIPZone
string
GetNatIPName
string
GetNatIPResult
string
GetNatIPErr
error
RunInstanceConfig
*
InstanceConfig
RunInstanceErrCh
<-
chan
error
RunInstanceErr
error
WaitForInstanceState
string
WaitForInstanceZone
string
WaitForInstanceName
string
WaitForInstanceErrCh
<-
chan
error
}
func
(
d
*
DriverMock
)
CreateImage
(
name
,
description
,
url
string
)
<-
chan
error
{
d
.
CreateImageName
=
name
d
.
CreateImageDesc
=
description
d
.
CreateImageURL
=
url
resultCh
:=
d
.
CreateImageErrCh
if
resultCh
==
nil
{
ch
:=
make
(
chan
error
)
close
(
ch
)
resultCh
=
ch
}
return
resultCh
}
func
(
d
*
DriverMock
)
DeleteImage
(
name
string
)
<-
chan
error
{
d
.
DeleteImageName
=
name
resultCh
:=
d
.
DeleteImageErrCh
if
resultCh
==
nil
{
ch
:=
make
(
chan
error
)
close
(
ch
)
resultCh
=
ch
}
return
resultCh
}
func
(
d
*
DriverMock
)
DeleteInstance
(
zone
,
name
string
)
(
<-
chan
error
,
error
)
{
d
.
DeleteInstanceZone
=
zone
d
.
DeleteInstanceName
=
name
resultCh
:=
d
.
DeleteInstanceErrCh
if
resultCh
==
nil
{
ch
:=
make
(
chan
error
)
close
(
ch
)
resultCh
=
ch
}
return
resultCh
,
d
.
DeleteInstanceErr
}
func
(
d
*
DriverMock
)
GetNatIP
(
zone
,
name
string
)
(
string
,
error
)
{
d
.
GetNatIPZone
=
zone
d
.
GetNatIPName
=
name
return
d
.
GetNatIPResult
,
d
.
GetNatIPErr
}
func
(
d
*
DriverMock
)
RunInstance
(
c
*
InstanceConfig
)
(
<-
chan
error
,
error
)
{
d
.
RunInstanceConfig
=
c
resultCh
:=
d
.
RunInstanceErrCh
if
resultCh
==
nil
{
ch
:=
make
(
chan
error
)
close
(
ch
)
resultCh
=
ch
}
return
resultCh
,
d
.
RunInstanceErr
}
func
(
d
*
DriverMock
)
WaitForInstance
(
state
,
zone
,
name
string
)
<-
chan
error
{
d
.
WaitForInstanceState
=
state
d
.
WaitForInstanceZone
=
zone
d
.
WaitForInstanceName
=
name
resultCh
:=
d
.
WaitForInstanceErrCh
if
resultCh
==
nil
{
ch
:=
make
(
chan
error
)
close
(
ch
)
resultCh
=
ch
}
return
resultCh
}
builder/googlecompute/private_key.go
0 → 100644
View file @
4f150dcc
package
googlecompute
import
(
"crypto/x509"
"encoding/pem"
"errors"
"fmt"
"io/ioutil"
)
// processPrivateKeyFile takes a private key file and an optional passphrase
// and decodes it to a byte slice.
func
processPrivateKeyFile
(
privateKeyFile
,
passphrase
string
)
([]
byte
,
error
)
{
rawPrivateKeyBytes
,
err
:=
ioutil
.
ReadFile
(
privateKeyFile
)
if
err
!=
nil
{
return
nil
,
fmt
.
Errorf
(
"Failed loading private key file: %s"
,
err
)
}
PEMBlock
,
_
:=
pem
.
Decode
(
rawPrivateKeyBytes
)
if
PEMBlock
==
nil
{
return
nil
,
fmt
.
Errorf
(
"%s does not contain a vaild private key"
,
privateKeyFile
)
}
if
x509
.
IsEncryptedPEMBlock
(
PEMBlock
)
{
if
passphrase
==
""
{
return
nil
,
errors
.
New
(
"a passphrase must be specified when using an encrypted private key"
)
}
decryptedPrivateKeyBytes
,
err
:=
x509
.
DecryptPEMBlock
(
PEMBlock
,
[]
byte
(
passphrase
))
if
err
!=
nil
{
return
nil
,
fmt
.
Errorf
(
"Failed decrypting private key: %s"
,
err
)
}
b
:=
&
pem
.
Block
{
Type
:
"RSA PRIVATE KEY"
,
Bytes
:
decryptedPrivateKeyBytes
,
}
return
pem
.
EncodeToMemory
(
b
),
nil
}
return
rawPrivateKeyBytes
,
nil
}
builder/googlecompute/private_key_test.go
0 → 100644
View file @
4f150dcc
package
googlecompute
import
(
"crypto/rand"
"crypto/x509"
"encoding/pem"
"io/ioutil"
"os"
"testing"
)
func
testPrivateKeyFile
(
t
*
testing
.
T
)
string
{
tf
,
err
:=
ioutil
.
TempFile
(
""
,
"packer"
)
if
err
!=
nil
{
t
.
Fatalf
(
"bad: %s"
,
err
)
}
defer
tf
.
Close
()
b
:=
&
pem
.
Block
{
Type
:
"RSA PRIVATE KEY"
,
Bytes
:
[]
byte
(
"what"
),
}
if
err
:=
pem
.
Encode
(
tf
,
b
);
err
!=
nil
{
t
.
Fatalf
(
"err: %s"
,
err
)
}
return
tf
.
Name
()
}
func
TestProcesssPrivateKeyFile
(
t
*
testing
.
T
)
{
path
:=
testPrivateKeyFile
(
t
)
defer
os
.
Remove
(
path
)
data
,
err
:=
processPrivateKeyFile
(
path
,
""
)
if
err
!=
nil
{
t
.
Fatalf
(
"err: %s"
,
err
)
}
if
len
(
data
)
<=
0
{
t
.
Fatalf
(
"bad: %#v"
,
data
)
}
}
func
TestProcessPrivateKeyFile_encrypted
(
t
*
testing
.
T
)
{
// Encrypt the file
b
,
err
:=
x509
.
EncryptPEMBlock
(
rand
.
Reader
,
"RSA PRIVATE KEY"
,
[]
byte
(
"what"
),
[]
byte
(
"password"
),
x509
.
PEMCipherAES128
)
if
err
!=
nil
{
t
.
Fatalf
(
"err: %s"
,
err
)
}
tf
,
err
:=
ioutil
.
TempFile
(
""
,
"packer"
)
if
err
!=
nil
{
t
.
Fatalf
(
"bad: %s"
,
err
)
}
defer
os
.
Remove
(
tf
.
Name
())
err
=
pem
.
Encode
(
tf
,
b
)
tf
.
Close
()
if
err
!=
nil
{
t
.
Fatalf
(
"err: %s"
,
err
)
}
path
:=
tf
.
Name
()
// Should have an error with a bad password
if
_
,
err
:=
processPrivateKeyFile
(
path
,
"bad"
);
err
==
nil
{
t
.
Fatal
(
"should error"
)
}
if
_
,
err
:=
processPrivateKeyFile
(
path
,
"password"
);
err
!=
nil
{
t
.
Fatalf
(
"bad: %s"
,
err
)
}
}
builder/googlecompute/ssh.go
0 → 100644
View file @
4f150dcc
package
googlecompute
import
(
"fmt"
gossh
"code.google.com/p/go.crypto/ssh"
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/communicator/ssh"
)
// sshAddress returns the ssh address.
func
sshAddress
(
state
multistep
.
StateBag
)
(
string
,
error
)
{
config
:=
state
.
Get
(
"config"
)
.
(
*
Config
)
ipAddress
:=
state
.
Get
(
"instance_ip"
)
.
(
string
)
return
fmt
.
Sprintf
(
"%s:%d"
,
ipAddress
,
config
.
SSHPort
),
nil
}
// sshConfig returns the ssh configuration.
func
sshConfig
(
state
multistep
.
StateBag
)
(
*
gossh
.
ClientConfig
,
error
)
{
config
:=
state
.
Get
(
"config"
)
.
(
*
Config
)
privateKey
:=
state
.
Get
(
"ssh_private_key"
)
.
(
string
)
keyring
:=
new
(
ssh
.
SimpleKeychain
)
if
err
:=
keyring
.
AddPEMKey
(
privateKey
);
err
!=
nil
{
return
nil
,
fmt
.
Errorf
(
"Error setting up SSH config: %s"
,
err
)
}
sshConfig
:=
&
gossh
.
ClientConfig
{
User
:
config
.
SSHUsername
,
Auth
:
[]
gossh
.
ClientAuth
{
gossh
.
ClientAuthKeyring
(
keyring
)},
}
return
sshConfig
,
nil
}
builder/googlecompute/step_create_image.go
0 → 100644
View file @
4f150dcc
package
googlecompute
import
(
"fmt"
"path/filepath"
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/packer"
)
// StepCreateImage represents a Packer build step that creates GCE machine
// images.
type
StepCreateImage
int
// Run executes the Packer build step that creates a GCE machine image.
//
// Currently the only way to create a GCE image is to run the gcimagebundle
// command on the running GCE instance.
func
(
s
*
StepCreateImage
)
Run
(
state
multistep
.
StateBag
)
multistep
.
StepAction
{
config
:=
state
.
Get
(
"config"
)
.
(
*
Config
)
comm
:=
state
.
Get
(
"communicator"
)
.
(
packer
.
Communicator
)
ui
:=
state
.
Get
(
"ui"
)
.
(
packer
.
Ui
)
sudoPrefix
:=
""
if
config
.
SSHUsername
!=
"root"
{
sudoPrefix
=
"sudo "
}
imageFilename
:=
fmt
.
Sprintf
(
"%s.tar.gz"
,
config
.
ImageName
)
imageBundleCmd
:=
"/usr/bin/gcimagebundle -d /dev/sda -o /tmp/"
ui
.
Say
(
"Creating image..."
)
cmd
:=
new
(
packer
.
RemoteCmd
)
cmd
.
Command
=
fmt
.
Sprintf
(
"%s%s --output_file_name %s"
,
sudoPrefix
,
imageBundleCmd
,
imageFilename
)
err
:=
cmd
.
StartWithUi
(
comm
,
ui
)
if
err
==
nil
&&
cmd
.
ExitStatus
!=
0
{
err
=
fmt
.
Errorf
(
"gcimagebundle exited with non-zero exit status: %d"
,
cmd
.
ExitStatus
)
}
if
err
!=
nil
{
err
:=
fmt
.
Errorf
(
"Error creating image: %s"
,
err
)
state
.
Put
(
"error"
,
err
)
ui
.
Error
(
err
.
Error
())
return
multistep
.
ActionHalt
}
state
.
Put
(
"image_file_name"
,
filepath
.
Join
(
"/tmp"
,
imageFilename
))
return
multistep
.
ActionContinue
}
func
(
s
*
StepCreateImage
)
Cleanup
(
state
multistep
.
StateBag
)
{}
builder/googlecompute/step_create_image_test.go
0 → 100644
View file @
4f150dcc
package
googlecompute
import
(
"strings"
"testing"
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/packer"
)
func
TestStepCreateImage_impl
(
t
*
testing
.
T
)
{
var
_
multistep
.
Step
=
new
(
StepCreateImage
)
}
func
TestStepCreateImage
(
t
*
testing
.
T
)
{
state
:=
testState
(
t
)
step
:=
new
(
StepCreateImage
)
defer
step
.
Cleanup
(
state
)
comm
:=
new
(
packer
.
MockCommunicator
)
state
.
Put
(
"communicator"
,
comm
)
// run the step
if
action
:=
step
.
Run
(
state
);
action
!=
multistep
.
ActionContinue
{
t
.
Fatalf
(
"bad action: %#v"
,
action
)
}
// Verify
if
!
comm
.
StartCalled
{
t
.
Fatal
(
"start should be called"
)
}
if
strings
.
HasPrefix
(
comm
.
StartCmd
.
Command
,
"sudo"
)
{
t
.
Fatal
(
"should not sudo"
)
}
if
!
strings
.
Contains
(
comm
.
StartCmd
.
Command
,
"gcimagebundle"
)
{
t
.
Fatalf
(
"bad command: %#v"
,
comm
.
StartCmd
.
Command
)
}
if
_
,
ok
:=
state
.
GetOk
(
"image_file_name"
);
!
ok
{
t
.
Fatal
(
"should have image"
)
}
}
func
TestStepCreateImage_badExitStatus
(
t
*
testing
.
T
)
{
state
:=
testState
(
t
)
step
:=
new
(
StepCreateImage
)
defer
step
.
Cleanup
(
state
)
comm
:=
new
(
packer
.
MockCommunicator
)
comm
.
StartExitStatus
=
12
state
.
Put
(
"communicator"
,
comm
)
// run the step
if
action
:=
step
.
Run
(
state
);
action
!=
multistep
.
ActionHalt
{
t
.
Fatalf
(
"bad action: %#v"
,
action
)
}
if
_
,
ok
:=
state
.
GetOk
(
"error"
);
!
ok
{
t
.
Fatal
(
"should have error"
)
}
if
_
,
ok
:=
state
.
GetOk
(
"image_file_name"
);
ok
{
t
.
Fatal
(
"should NOT have image"
)
}
}
func
TestStepCreateImage_nonRoot
(
t
*
testing
.
T
)
{
state
:=
testState
(
t
)
step
:=
new
(
StepCreateImage
)
defer
step
.
Cleanup
(
state
)
comm
:=
new
(
packer
.
MockCommunicator
)
state
.
Put
(
"communicator"
,
comm
)
config
:=
state
.
Get
(
"config"
)
.
(
*
Config
)
config
.
SSHUsername
=
"bob"
// run the step
if
action
:=
step
.
Run
(
state
);
action
!=
multistep
.
ActionContinue
{
t
.
Fatalf
(
"bad action: %#v"
,
action
)
}
// Verify
if
!
comm
.
StartCalled
{
t
.
Fatal
(
"start should be called"
)
}
if
!
strings
.
HasPrefix
(
comm
.
StartCmd
.
Command
,
"sudo"
)
{
t
.
Fatal
(
"should sudo"
)
}
if
!
strings
.
Contains
(
comm
.
StartCmd
.
Command
,
"gcimagebundle"
)
{
t
.
Fatalf
(
"bad command: %#v"
,
comm
.
StartCmd
.
Command
)
}
if
_
,
ok
:=
state
.
GetOk
(
"image_file_name"
);
!
ok
{
t
.
Fatal
(
"should have image"
)
}
}
builder/googlecompute/step_create_instance.go
0 → 100644
View file @
4f150dcc
package
googlecompute
import
(
"errors"
"fmt"
"time"
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/common/uuid"
"github.com/mitchellh/packer/packer"
)
// StepCreateInstance represents a Packer build step that creates GCE instances.
type
StepCreateInstance
struct
{
instanceName
string
}
// Run executes the Packer build step that creates a GCE instance.
func
(
s
*
StepCreateInstance
)
Run
(
state
multistep
.
StateBag
)
multistep
.
StepAction
{
config
:=
state
.
Get
(
"config"
)
.
(
*
Config
)
driver
:=
state
.
Get
(
"driver"
)
.
(
Driver
)
sshPublicKey
:=
state
.
Get
(
"ssh_public_key"
)
.
(
string
)
ui
:=
state
.
Get
(
"ui"
)
.
(
packer
.
Ui
)
ui
.
Say
(
"Creating instance..."
)
name
:=
fmt
.
Sprintf
(
"packer-%s"
,
uuid
.
TimeOrderedUUID
())
errCh
,
err
:=
driver
.
RunInstance
(
&
InstanceConfig
{
Description
:
"New instance created by Packer"
,
Image
:
config
.
SourceImage
,
MachineType
:
config
.
MachineType
,
Metadata
:
map
[
string
]
string
{
"sshKeys"
:
fmt
.
Sprintf
(
"%s:%s"
,
config
.
SSHUsername
,
sshPublicKey
),
},
Name
:
name
,
Network
:
config
.
Network
,
Tags
:
config
.
Tags
,
Zone
:
config
.
Zone
,
})
if
err
==
nil
{
ui
.
Message
(
"Waiting for creation operation to complete..."
)
select
{
case
err
=
<-
errCh
:
case
<-
time
.
After
(
config
.
stateTimeout
)
:
err
=
errors
.
New
(
"time out while waiting for instance to create"
)
}
}
if
err
!=
nil
{
err
:=
fmt
.
Errorf
(
"Error creating instance: %s"
,
err
)
state
.
Put
(
"error"
,
err
)
ui
.
Error
(
err
.
Error
())
return
multistep
.
ActionHalt
}
ui
.
Message
(
"Instance has been created!"
)
// Things succeeded, store the name so we can remove it later
state
.
Put
(
"instance_name"
,
name
)
s
.
instanceName
=
name
return
multistep
.
ActionContinue
}
// Cleanup destroys the GCE instance created during the image creation process.
func
(
s
*
StepCreateInstance
)
Cleanup
(
state
multistep
.
StateBag
)
{
if
s
.
instanceName
==
""
{
return
}
config
:=
state
.
Get
(
"config"
)
.
(
*
Config
)
driver
:=
state
.
Get
(
"driver"
)
.
(
Driver
)
ui
:=
state
.
Get
(
"ui"
)
.
(
packer
.
Ui
)
ui
.
Say
(
"Deleting instance..."
)
errCh
,
err
:=
driver
.
DeleteInstance
(
config
.
Zone
,
s
.
instanceName
)
if
err
==
nil
{
select
{
case
err
=
<-
errCh
:
case
<-
time
.
After
(
config
.
stateTimeout
)
:
err
=
errors
.
New
(
"time out while waiting for instance to delete"
)
}
}
if
err
!=
nil
{
ui
.
Error
(
fmt
.
Sprintf
(
"Error deleting instance. Please delete it manually.
\n\n
"
+
"Name: %s
\n
"
+
"Error: %s"
,
s
.
instanceName
,
err
))
}
s
.
instanceName
=
""
return
}
builder/googlecompute/step_create_instance_test.go
0 → 100644
View file @
4f150dcc
package
googlecompute
import
(
"errors"
"github.com/mitchellh/multistep"
"testing"
"time"
)
func
TestStepCreateInstance_impl
(
t
*
testing
.
T
)
{
var
_
multistep
.
Step
=
new
(
StepCreateInstance
)
}
func
TestStepCreateInstance
(
t
*
testing
.
T
)
{
state
:=
testState
(
t
)
step
:=
new
(
StepCreateInstance
)
defer
step
.
Cleanup
(
state
)
state
.
Put
(
"ssh_public_key"
,
"key"
)
config
:=
state
.
Get
(
"config"
)
.
(
*
Config
)
driver
:=
state
.
Get
(
"driver"
)
.
(
*
DriverMock
)
// run the step
if
action
:=
step
.
Run
(
state
);
action
!=
multistep
.
ActionContinue
{
t
.
Fatalf
(
"bad action: %#v"
,
action
)
}
// Verify state
nameRaw
,
ok
:=
state
.
GetOk
(
"instance_name"
)
if
!
ok
{
t
.
Fatal
(
"should have instance name"
)
}
// cleanup
step
.
Cleanup
(
state
)
if
driver
.
DeleteInstanceName
!=
nameRaw
.
(
string
)
{
t
.
Fatal
(
"should've deleted instance"
)
}
if
driver
.
DeleteInstanceZone
!=
config
.
Zone
{
t
.
Fatal
(
"bad zone: %#v"
,
driver
.
DeleteInstanceZone
)
}
}
func
TestStepCreateInstance_error
(
t
*
testing
.
T
)
{
state
:=
testState
(
t
)
step
:=
new
(
StepCreateInstance
)
defer
step
.
Cleanup
(
state
)
state
.
Put
(
"ssh_public_key"
,
"key"
)
driver
:=
state
.
Get
(
"driver"
)
.
(
*
DriverMock
)
driver
.
RunInstanceErr
=
errors
.
New
(
"error"
)
// run the step
if
action
:=
step
.
Run
(
state
);
action
!=
multistep
.
ActionHalt
{
t
.
Fatalf
(
"bad action: %#v"
,
action
)
}
// Verify state
if
_
,
ok
:=
state
.
GetOk
(
"error"
);
!
ok
{
t
.
Fatal
(
"should have error"
)
}
if
_
,
ok
:=
state
.
GetOk
(
"instance_name"
);
ok
{
t
.
Fatal
(
"should NOT have instance name"
)
}
}
func
TestStepCreateInstance_errorOnChannel
(
t
*
testing
.
T
)
{
state
:=
testState
(
t
)
step
:=
new
(
StepCreateInstance
)
defer
step
.
Cleanup
(
state
)
errCh
:=
make
(
chan
error
,
1
)
errCh
<-
errors
.
New
(
"error"
)
state
.
Put
(
"ssh_public_key"
,
"key"
)
driver
:=
state
.
Get
(
"driver"
)
.
(
*
DriverMock
)
driver
.
RunInstanceErrCh
=
errCh
// run the step
if
action
:=
step
.
Run
(
state
);
action
!=
multistep
.
ActionHalt
{
t
.
Fatalf
(
"bad action: %#v"
,
action
)
}
// Verify state
if
_
,
ok
:=
state
.
GetOk
(
"error"
);
!
ok
{
t
.
Fatal
(
"should have error"
)
}
if
_
,
ok
:=
state
.
GetOk
(
"instance_name"
);
ok
{
t
.
Fatal
(
"should NOT have instance name"
)
}
}
func
TestStepCreateInstance_errorTimeout
(
t
*
testing
.
T
)
{
state
:=
testState
(
t
)
step
:=
new
(
StepCreateInstance
)
defer
step
.
Cleanup
(
state
)
errCh
:=
make
(
chan
error
,
1
)
go
func
()
{
<-
time
.
After
(
10
*
time
.
Millisecond
)
errCh
<-
nil
}()
state
.
Put
(
"ssh_public_key"
,
"key"
)
config
:=
state
.
Get
(
"config"
)
.
(
*
Config
)
config
.
stateTimeout
=
1
*
time
.
Microsecond
driver
:=
state
.
Get
(
"driver"
)
.
(
*
DriverMock
)
driver
.
RunInstanceErrCh
=
errCh
// run the step
if
action
:=
step
.
Run
(
state
);
action
!=
multistep
.
ActionHalt
{
t
.
Fatalf
(
"bad action: %#v"
,
action
)
}
// Verify state
if
_
,
ok
:=
state
.
GetOk
(
"error"
);
!
ok
{
t
.
Fatal
(
"should have error"
)
}
if
_
,
ok
:=
state
.
GetOk
(
"instance_name"
);
ok
{
t
.
Fatal
(
"should NOT have instance name"
)
}
}
builder/googlecompute/step_create_ssh_key.go
0 → 100644
View file @
4f150dcc
package
googlecompute
import
(
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"encoding/pem"
"fmt"
"code.google.com/p/go.crypto/ssh"
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/packer"
)
// StepCreateSSHKey represents a Packer build step that generates SSH key pairs.
type
StepCreateSSHKey
int
// Run executes the Packer build step that generates SSH key pairs.
func
(
s
*
StepCreateSSHKey
)
Run
(
state
multistep
.
StateBag
)
multistep
.
StepAction
{
ui
:=
state
.
Get
(
"ui"
)
.
(
packer
.
Ui
)
ui
.
Say
(
"Creating temporary SSH key for instance..."
)
priv
,
err
:=
rsa
.
GenerateKey
(
rand
.
Reader
,
2048
)
if
err
!=
nil
{
err
:=
fmt
.
Errorf
(
"Error creating temporary ssh key: %s"
,
err
)
state
.
Put
(
"error"
,
err
)
ui
.
Error
(
err
.
Error
())
return
multistep
.
ActionHalt
}
priv_blk
:=
pem
.
Block
{
Type
:
"RSA PRIVATE KEY"
,
Headers
:
nil
,
Bytes
:
x509
.
MarshalPKCS1PrivateKey
(
priv
),
}
pub
,
err
:=
ssh
.
NewPublicKey
(
&
priv
.
PublicKey
)
if
err
!=
nil
{
err
:=
fmt
.
Errorf
(
"Error creating temporary ssh key: %s"
,
err
)
state
.
Put
(
"error"
,
err
)
ui
.
Error
(
err
.
Error
())
return
multistep
.
ActionHalt
}
state
.
Put
(
"ssh_private_key"
,
string
(
pem
.
EncodeToMemory
(
&
priv_blk
)))
state
.
Put
(
"ssh_public_key"
,
string
(
ssh
.
MarshalAuthorizedKey
(
pub
)))
return
multistep
.
ActionContinue
}
// Nothing to clean up. SSH keys are associated with a single GCE instance.
func
(
s
*
StepCreateSSHKey
)
Cleanup
(
state
multistep
.
StateBag
)
{}
builder/googlecompute/step_create_ssh_key_test.go
0 → 100644
View file @
4f150dcc
package
googlecompute
import
(
"github.com/mitchellh/multistep"
"testing"
)
func
TestStepCreateSSHKey_impl
(
t
*
testing
.
T
)
{
var
_
multistep
.
Step
=
new
(
StepCreateSSHKey
)
}
func
TestStepCreateSSHKey
(
t
*
testing
.
T
)
{
state
:=
testState
(
t
)
step
:=
new
(
StepCreateSSHKey
)
defer
step
.
Cleanup
(
state
)
// run the step
if
action
:=
step
.
Run
(
state
);
action
!=
multistep
.
ActionContinue
{
t
.
Fatalf
(
"bad action: %#v"
,
action
)
}
// Verify that we have a public/private key
if
_
,
ok
:=
state
.
GetOk
(
"ssh_private_key"
);
!
ok
{
t
.
Fatal
(
"should have key"
)
}
if
_
,
ok
:=
state
.
GetOk
(
"ssh_public_key"
);
!
ok
{
t
.
Fatal
(
"should have key"
)
}
}
builder/googlecompute/step_instance_info.go
0 → 100644
View file @
4f150dcc
package
googlecompute
import
(
"errors"
"fmt"
"time"
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/packer"
)
// stepInstanceInfo represents a Packer build step that gathers GCE instance info.
type
StepInstanceInfo
int
// Run executes the Packer build step that gathers GCE instance info.
func
(
s
*
StepInstanceInfo
)
Run
(
state
multistep
.
StateBag
)
multistep
.
StepAction
{
config
:=
state
.
Get
(
"config"
)
.
(
*
Config
)
driver
:=
state
.
Get
(
"driver"
)
.
(
Driver
)
ui
:=
state
.
Get
(
"ui"
)
.
(
packer
.
Ui
)
instanceName
:=
state
.
Get
(
"instance_name"
)
.
(
string
)
ui
.
Say
(
"Waiting for the instance to become running..."
)
errCh
:=
driver
.
WaitForInstance
(
"RUNNING"
,
config
.
Zone
,
instanceName
)
var
err
error
select
{
case
err
=
<-
errCh
:
case
<-
time
.
After
(
config
.
stateTimeout
)
:
err
=
errors
.
New
(
"time out while waiting for instance to become running"
)
}
if
err
!=
nil
{
err
:=
fmt
.
Errorf
(
"Error waiting for instance: %s"
,
err
)
state
.
Put
(
"error"
,
err
)
ui
.
Error
(
err
.
Error
())
return
multistep
.
ActionHalt
}
ip
,
err
:=
driver
.
GetNatIP
(
config
.
Zone
,
instanceName
)
if
err
!=
nil
{
err
:=
fmt
.
Errorf
(
"Error retrieving instance nat ip address: %s"
,
err
)
state
.
Put
(
"error"
,
err
)
ui
.
Error
(
err
.
Error
())
return
multistep
.
ActionHalt
}
ui
.
Message
(
fmt
.
Sprintf
(
"IP: %s"
,
ip
))
state
.
Put
(
"instance_ip"
,
ip
)
return
multistep
.
ActionContinue
}
// Cleanup.
func
(
s
*
StepInstanceInfo
)
Cleanup
(
state
multistep
.
StateBag
)
{}
builder/googlecompute/step_instance_info_test.go
0 → 100644
View file @
4f150dcc
package
googlecompute
import
(
"errors"
"github.com/mitchellh/multistep"
"testing"
"time"
)
func
TestStepInstanceInfo_impl
(
t
*
testing
.
T
)
{
var
_
multistep
.
Step
=
new
(
StepInstanceInfo
)
}
func
TestStepInstanceInfo
(
t
*
testing
.
T
)
{
state
:=
testState
(
t
)
step
:=
new
(
StepInstanceInfo
)
defer
step
.
Cleanup
(
state
)
state
.
Put
(
"instance_name"
,
"foo"
)
config
:=
state
.
Get
(
"config"
)
.
(
*
Config
)
driver
:=
state
.
Get
(
"driver"
)
.
(
*
DriverMock
)
driver
.
GetNatIPResult
=
"1.2.3.4"
// run the step
if
action
:=
step
.
Run
(
state
);
action
!=
multistep
.
ActionContinue
{
t
.
Fatalf
(
"bad action: %#v"
,
action
)
}
// Verify state
if
driver
.
WaitForInstanceState
!=
"RUNNING"
{
t
.
Fatalf
(
"bad: %#v"
,
driver
.
WaitForInstanceState
)
}
if
driver
.
WaitForInstanceZone
!=
config
.
Zone
{
t
.
Fatalf
(
"bad: %#v"
,
driver
.
WaitForInstanceZone
)
}
if
driver
.
WaitForInstanceName
!=
"foo"
{
t
.
Fatalf
(
"bad: %#v"
,
driver
.
WaitForInstanceName
)
}
ipRaw
,
ok
:=
state
.
GetOk
(
"instance_ip"
)
if
!
ok
{
t
.
Fatal
(
"should have ip"
)
}
if
ip
,
ok
:=
ipRaw
.
(
string
);
!
ok
{
t
.
Fatal
(
"ip is not a string"
)
}
else
if
ip
!=
"1.2.3.4"
{
t
.
Fatalf
(
"bad ip: %s"
,
ip
)
}
}
func
TestStepInstanceInfo_getNatIPError
(
t
*
testing
.
T
)
{
state
:=
testState
(
t
)
step
:=
new
(
StepInstanceInfo
)
defer
step
.
Cleanup
(
state
)
state
.
Put
(
"instance_name"
,
"foo"
)
driver
:=
state
.
Get
(
"driver"
)
.
(
*
DriverMock
)
driver
.
GetNatIPErr
=
errors
.
New
(
"error"
)
// run the step
if
action
:=
step
.
Run
(
state
);
action
!=
multistep
.
ActionHalt
{
t
.
Fatalf
(
"bad action: %#v"
,
action
)
}
// Verify state
if
_
,
ok
:=
state
.
GetOk
(
"error"
);
!
ok
{
t
.
Fatal
(
"should have error"
)
}
if
_
,
ok
:=
state
.
GetOk
(
"instance_ip"
);
ok
{
t
.
Fatal
(
"should NOT have instance IP"
)
}
}
func
TestStepInstanceInfo_waitError
(
t
*
testing
.
T
)
{
state
:=
testState
(
t
)
step
:=
new
(
StepInstanceInfo
)
defer
step
.
Cleanup
(
state
)
state
.
Put
(
"instance_name"
,
"foo"
)
errCh
:=
make
(
chan
error
,
1
)
errCh
<-
errors
.
New
(
"error"
)
driver
:=
state
.
Get
(
"driver"
)
.
(
*
DriverMock
)
driver
.
WaitForInstanceErrCh
=
errCh
// run the step
if
action
:=
step
.
Run
(
state
);
action
!=
multistep
.
ActionHalt
{
t
.
Fatalf
(
"bad action: %#v"
,
action
)
}
// Verify state
if
_
,
ok
:=
state
.
GetOk
(
"error"
);
!
ok
{
t
.
Fatal
(
"should have error"
)
}
if
_
,
ok
:=
state
.
GetOk
(
"instance_ip"
);
ok
{
t
.
Fatal
(
"should NOT have instance IP"
)
}
}
func
TestStepInstanceInfo_errorTimeout
(
t
*
testing
.
T
)
{
state
:=
testState
(
t
)
step
:=
new
(
StepInstanceInfo
)
defer
step
.
Cleanup
(
state
)
errCh
:=
make
(
chan
error
,
1
)
go
func
()
{
<-
time
.
After
(
10
*
time
.
Millisecond
)
errCh
<-
nil
}()
state
.
Put
(
"instance_name"
,
"foo"
)
config
:=
state
.
Get
(
"config"
)
.
(
*
Config
)
config
.
stateTimeout
=
1
*
time
.
Microsecond
driver
:=
state
.
Get
(
"driver"
)
.
(
*
DriverMock
)
driver
.
WaitForInstanceErrCh
=
errCh
// run the step
if
action
:=
step
.
Run
(
state
);
action
!=
multistep
.
ActionHalt
{
t
.
Fatalf
(
"bad action: %#v"
,
action
)
}
// Verify state
if
_
,
ok
:=
state
.
GetOk
(
"error"
);
!
ok
{
t
.
Fatal
(
"should have error"
)
}
if
_
,
ok
:=
state
.
GetOk
(
"instance_ip"
);
ok
{
t
.
Fatal
(
"should NOT have instance IP"
)
}
}
builder/googlecompute/step_register_image.go
0 → 100644
View file @
4f150dcc
package
googlecompute
import
(
"errors"
"fmt"
"time"
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/packer"
)
// StepRegisterImage represents a Packer build step that registers GCE machine images.
type
StepRegisterImage
int
// Run executes the Packer build step that registers a GCE machine image.
func
(
s
*
StepRegisterImage
)
Run
(
state
multistep
.
StateBag
)
multistep
.
StepAction
{
config
:=
state
.
Get
(
"config"
)
.
(
*
Config
)
driver
:=
state
.
Get
(
"driver"
)
.
(
Driver
)
ui
:=
state
.
Get
(
"ui"
)
.
(
packer
.
Ui
)
var
err
error
imageURL
:=
fmt
.
Sprintf
(
"https://storage.cloud.google.com/%s/%s.tar.gz"
,
config
.
BucketName
,
config
.
ImageName
)
ui
.
Say
(
"Registering image..."
)
errCh
:=
driver
.
CreateImage
(
config
.
ImageName
,
config
.
ImageDescription
,
imageURL
)
select
{
case
err
=
<-
errCh
:
case
<-
time
.
After
(
config
.
stateTimeout
)
:
err
=
errors
.
New
(
"time out while waiting for image to register"
)
}
if
err
!=
nil
{
err
:=
fmt
.
Errorf
(
"Error waiting for image: %s"
,
err
)
state
.
Put
(
"error"
,
err
)
ui
.
Error
(
err
.
Error
())
return
multistep
.
ActionHalt
}
state
.
Put
(
"image_name"
,
config
.
ImageName
)
return
multistep
.
ActionContinue
}
// Cleanup.
func
(
s
*
StepRegisterImage
)
Cleanup
(
state
multistep
.
StateBag
)
{}
builder/googlecompute/step_register_image_test.go
0 → 100644
View file @
4f150dcc
package
googlecompute
import
(
"errors"
"github.com/mitchellh/multistep"
"testing"
"time"
)
func
TestStepRegisterImage_impl
(
t
*
testing
.
T
)
{
var
_
multistep
.
Step
=
new
(
StepRegisterImage
)
}
func
TestStepRegisterImage
(
t
*
testing
.
T
)
{
state
:=
testState
(
t
)
step
:=
new
(
StepRegisterImage
)
defer
step
.
Cleanup
(
state
)
config
:=
state
.
Get
(
"config"
)
.
(
*
Config
)
driver
:=
state
.
Get
(
"driver"
)
.
(
*
DriverMock
)
// run the step
if
action
:=
step
.
Run
(
state
);
action
!=
multistep
.
ActionContinue
{
t
.
Fatalf
(
"bad action: %#v"
,
action
)
}
// Verify state
if
driver
.
CreateImageName
!=
config
.
ImageName
{
t
.
Fatalf
(
"bad: %#v"
,
driver
.
CreateImageName
)
}
if
driver
.
CreateImageDesc
!=
config
.
ImageDescription
{
t
.
Fatalf
(
"bad: %#v"
,
driver
.
CreateImageDesc
)
}
nameRaw
,
ok
:=
state
.
GetOk
(
"image_name"
)
if
!
ok
{
t
.
Fatal
(
"should have name"
)
}
if
name
,
ok
:=
nameRaw
.
(
string
);
!
ok
{
t
.
Fatal
(
"name is not a string"
)
}
else
if
name
!=
config
.
ImageName
{
t
.
Fatalf
(
"bad name: %s"
,
name
)
}
}
func
TestStepRegisterImage_waitError
(
t
*
testing
.
T
)
{
state
:=
testState
(
t
)
step
:=
new
(
StepRegisterImage
)
defer
step
.
Cleanup
(
state
)
errCh
:=
make
(
chan
error
,
1
)
errCh
<-
errors
.
New
(
"error"
)
driver
:=
state
.
Get
(
"driver"
)
.
(
*
DriverMock
)
driver
.
CreateImageErrCh
=
errCh
// run the step
if
action
:=
step
.
Run
(
state
);
action
!=
multistep
.
ActionHalt
{
t
.
Fatalf
(
"bad action: %#v"
,
action
)
}
// Verify state
if
_
,
ok
:=
state
.
GetOk
(
"error"
);
!
ok
{
t
.
Fatal
(
"should have error"
)
}
if
_
,
ok
:=
state
.
GetOk
(
"image_name"
);
ok
{
t
.
Fatal
(
"should NOT have image_name"
)
}
}
func
TestStepRegisterImage_errorTimeout
(
t
*
testing
.
T
)
{
state
:=
testState
(
t
)
step
:=
new
(
StepRegisterImage
)
defer
step
.
Cleanup
(
state
)
errCh
:=
make
(
chan
error
,
1
)
go
func
()
{
<-
time
.
After
(
10
*
time
.
Millisecond
)
errCh
<-
nil
}()
config
:=
state
.
Get
(
"config"
)
.
(
*
Config
)
config
.
stateTimeout
=
1
*
time
.
Microsecond
driver
:=
state
.
Get
(
"driver"
)
.
(
*
DriverMock
)
driver
.
CreateImageErrCh
=
errCh
// run the step
if
action
:=
step
.
Run
(
state
);
action
!=
multistep
.
ActionHalt
{
t
.
Fatalf
(
"bad action: %#v"
,
action
)
}
// Verify state
if
_
,
ok
:=
state
.
GetOk
(
"error"
);
!
ok
{
t
.
Fatal
(
"should have error"
)
}
if
_
,
ok
:=
state
.
GetOk
(
"image_name"
);
ok
{
t
.
Fatal
(
"should NOT have image name"
)
}
}
builder/googlecompute/step_test.go
0 → 100644
View file @
4f150dcc
package
googlecompute
import
(
"bytes"
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/packer"
"testing"
)
func
testState
(
t
*
testing
.
T
)
multistep
.
StateBag
{
state
:=
new
(
multistep
.
BasicStateBag
)
state
.
Put
(
"config"
,
testConfigStruct
(
t
))
state
.
Put
(
"driver"
,
&
DriverMock
{})
state
.
Put
(
"hook"
,
&
packer
.
MockHook
{})
state
.
Put
(
"ui"
,
&
packer
.
BasicUi
{
Reader
:
new
(
bytes
.
Buffer
),
Writer
:
new
(
bytes
.
Buffer
),
})
return
state
}
builder/googlecompute/step_update_gsutil.go
0 → 100644
View file @
4f150dcc
package
googlecompute
import
(
"fmt"
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/packer"
)
// StepUpdateGsutil represents a Packer build step that updates the gsutil
// utility to the latest version available.
type
StepUpdateGsutil
int
// Run executes the Packer build step that updates the gsutil utility to the
// latest version available.
//
// This step is required to prevent the image creation process from hanging;
// the image creation process utilizes the gcimagebundle cli tool which will
// prompt to update gsutil if a newer version is available.
func
(
s
*
StepUpdateGsutil
)
Run
(
state
multistep
.
StateBag
)
multistep
.
StepAction
{
comm
:=
state
.
Get
(
"communicator"
)
.
(
packer
.
Communicator
)
config
:=
state
.
Get
(
"config"
)
.
(
*
Config
)
ui
:=
state
.
Get
(
"ui"
)
.
(
packer
.
Ui
)
sudoPrefix
:=
""
if
config
.
SSHUsername
!=
"root"
{
sudoPrefix
=
"sudo "
}
gsutilUpdateCmd
:=
"/usr/local/bin/gsutil update -n -f"
cmd
:=
new
(
packer
.
RemoteCmd
)
cmd
.
Command
=
fmt
.
Sprintf
(
"%s%s"
,
sudoPrefix
,
gsutilUpdateCmd
)
ui
.
Say
(
"Updating gsutil..."
)
err
:=
cmd
.
StartWithUi
(
comm
,
ui
)
if
err
==
nil
&&
cmd
.
ExitStatus
!=
0
{
err
=
fmt
.
Errorf
(
"gsutil update exited with non-zero exit status: %d"
,
cmd
.
ExitStatus
)
}
if
err
!=
nil
{
err
:=
fmt
.
Errorf
(
"Error updating gsutil: %s"
,
err
)
state
.
Put
(
"error"
,
err
)
ui
.
Error
(
err
.
Error
())
return
multistep
.
ActionHalt
}
return
multistep
.
ActionContinue
}
// Cleanup.
func
(
s
*
StepUpdateGsutil
)
Cleanup
(
state
multistep
.
StateBag
)
{}
builder/googlecompute/step_update_gsutil_test.go
0 → 100644
View file @
4f150dcc
package
googlecompute
import
(
"strings"
"testing"
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/packer"
)
func
TestStepUpdateGsutil_impl
(
t
*
testing
.
T
)
{
var
_
multistep
.
Step
=
new
(
StepUpdateGsutil
)
}
func
TestStepUpdateGsutil
(
t
*
testing
.
T
)
{
state
:=
testState
(
t
)
step
:=
new
(
StepUpdateGsutil
)
defer
step
.
Cleanup
(
state
)
comm
:=
new
(
packer
.
MockCommunicator
)
state
.
Put
(
"communicator"
,
comm
)
// run the step
if
action
:=
step
.
Run
(
state
);
action
!=
multistep
.
ActionContinue
{
t
.
Fatalf
(
"bad action: %#v"
,
action
)
}
// Verify
if
!
comm
.
StartCalled
{
t
.
Fatal
(
"start should be called"
)
}
if
strings
.
HasPrefix
(
comm
.
StartCmd
.
Command
,
"sudo"
)
{
t
.
Fatal
(
"should not sudo"
)
}
if
!
strings
.
Contains
(
comm
.
StartCmd
.
Command
,
"gsutil update"
)
{
t
.
Fatalf
(
"bad command: %#v"
,
comm
.
StartCmd
.
Command
)
}
}
func
TestStepUpdateGsutil_badExitStatus
(
t
*
testing
.
T
)
{
state
:=
testState
(
t
)
step
:=
new
(
StepUpdateGsutil
)
defer
step
.
Cleanup
(
state
)
comm
:=
new
(
packer
.
MockCommunicator
)
comm
.
StartExitStatus
=
12
state
.
Put
(
"communicator"
,
comm
)
// run the step
if
action
:=
step
.
Run
(
state
);
action
!=
multistep
.
ActionHalt
{
t
.
Fatalf
(
"bad action: %#v"
,
action
)
}
if
_
,
ok
:=
state
.
GetOk
(
"error"
);
!
ok
{
t
.
Fatal
(
"should have error"
)
}
}
func
TestStepUpdateGsutil_nonRoot
(
t
*
testing
.
T
)
{
state
:=
testState
(
t
)
step
:=
new
(
StepUpdateGsutil
)
defer
step
.
Cleanup
(
state
)
comm
:=
new
(
packer
.
MockCommunicator
)
state
.
Put
(
"communicator"
,
comm
)
config
:=
state
.
Get
(
"config"
)
.
(
*
Config
)
config
.
SSHUsername
=
"bob"
// run the step
if
action
:=
step
.
Run
(
state
);
action
!=
multistep
.
ActionContinue
{
t
.
Fatalf
(
"bad action: %#v"
,
action
)
}
// Verify
if
!
comm
.
StartCalled
{
t
.
Fatal
(
"start should be called"
)
}
if
!
strings
.
HasPrefix
(
comm
.
StartCmd
.
Command
,
"sudo"
)
{
t
.
Fatal
(
"should sudo"
)
}
if
!
strings
.
Contains
(
comm
.
StartCmd
.
Command
,
"gsutil update"
)
{
t
.
Fatalf
(
"bad command: %#v"
,
comm
.
StartCmd
.
Command
)
}
}
builder/googlecompute/step_upload_image.go
0 → 100644
View file @
4f150dcc
package
googlecompute
import
(
"fmt"
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/packer"
)
// StepUploadImage represents a Packer build step that uploads GCE machine images.
type
StepUploadImage
int
// Run executes the Packer build step that uploads a GCE machine image.
func
(
s
*
StepUploadImage
)
Run
(
state
multistep
.
StateBag
)
multistep
.
StepAction
{
comm
:=
state
.
Get
(
"communicator"
)
.
(
packer
.
Communicator
)
config
:=
state
.
Get
(
"config"
)
.
(
*
Config
)
imageFilename
:=
state
.
Get
(
"image_file_name"
)
.
(
string
)
ui
:=
state
.
Get
(
"ui"
)
.
(
packer
.
Ui
)
sudoPrefix
:=
""
if
config
.
SSHUsername
!=
"root"
{
sudoPrefix
=
"sudo "
}
ui
.
Say
(
"Uploading image..."
)
cmd
:=
new
(
packer
.
RemoteCmd
)
cmd
.
Command
=
fmt
.
Sprintf
(
"%s/usr/local/bin/gsutil cp %s gs://%s"
,
sudoPrefix
,
imageFilename
,
config
.
BucketName
)
err
:=
cmd
.
StartWithUi
(
comm
,
ui
)
if
err
==
nil
&&
cmd
.
ExitStatus
!=
0
{
err
=
fmt
.
Errorf
(
"gsutil exited with non-zero exit status: %d"
,
cmd
.
ExitStatus
)
}
if
err
!=
nil
{
err
:=
fmt
.
Errorf
(
"Error uploading image: %s"
,
err
)
state
.
Put
(
"error"
,
err
)
ui
.
Error
(
err
.
Error
())
return
multistep
.
ActionHalt
}
return
multistep
.
ActionContinue
}
// Cleanup.
func
(
s
*
StepUploadImage
)
Cleanup
(
state
multistep
.
StateBag
)
{}
builder/googlecompute/step_upload_image_test.go
0 → 100644
View file @
4f150dcc
package
googlecompute
import
(
"strings"
"testing"
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/packer"
)
func
TestStepUploadImage_impl
(
t
*
testing
.
T
)
{
var
_
multistep
.
Step
=
new
(
StepUploadImage
)
}
func
TestStepUploadImage
(
t
*
testing
.
T
)
{
state
:=
testState
(
t
)
step
:=
new
(
StepUploadImage
)
defer
step
.
Cleanup
(
state
)
comm
:=
new
(
packer
.
MockCommunicator
)
state
.
Put
(
"communicator"
,
comm
)
state
.
Put
(
"image_file_name"
,
"foo"
)
// run the step
if
action
:=
step
.
Run
(
state
);
action
!=
multistep
.
ActionContinue
{
t
.
Fatalf
(
"bad action: %#v"
,
action
)
}
// Verify
if
!
comm
.
StartCalled
{
t
.
Fatal
(
"start should be called"
)
}
if
strings
.
HasPrefix
(
comm
.
StartCmd
.
Command
,
"sudo"
)
{
t
.
Fatal
(
"should not sudo"
)
}
if
!
strings
.
Contains
(
comm
.
StartCmd
.
Command
,
"gsutil cp"
)
{
t
.
Fatalf
(
"bad command: %#v"
,
comm
.
StartCmd
.
Command
)
}
}
func
TestStepUploadImage_badExitStatus
(
t
*
testing
.
T
)
{
state
:=
testState
(
t
)
step
:=
new
(
StepUploadImage
)
defer
step
.
Cleanup
(
state
)
comm
:=
new
(
packer
.
MockCommunicator
)
comm
.
StartExitStatus
=
12
state
.
Put
(
"communicator"
,
comm
)
state
.
Put
(
"image_file_name"
,
"foo"
)
// run the step
if
action
:=
step
.
Run
(
state
);
action
!=
multistep
.
ActionHalt
{
t
.
Fatalf
(
"bad action: %#v"
,
action
)
}
if
_
,
ok
:=
state
.
GetOk
(
"error"
);
!
ok
{
t
.
Fatal
(
"should have error"
)
}
}
func
TestStepUploadImage_nonRoot
(
t
*
testing
.
T
)
{
state
:=
testState
(
t
)
step
:=
new
(
StepUploadImage
)
defer
step
.
Cleanup
(
state
)
comm
:=
new
(
packer
.
MockCommunicator
)
state
.
Put
(
"communicator"
,
comm
)
state
.
Put
(
"image_file_name"
,
"foo"
)
config
:=
state
.
Get
(
"config"
)
.
(
*
Config
)
config
.
SSHUsername
=
"bob"
// run the step
if
action
:=
step
.
Run
(
state
);
action
!=
multistep
.
ActionContinue
{
t
.
Fatalf
(
"bad action: %#v"
,
action
)
}
// Verify
if
!
comm
.
StartCalled
{
t
.
Fatal
(
"start should be called"
)
}
if
!
strings
.
HasPrefix
(
comm
.
StartCmd
.
Command
,
"sudo"
)
{
t
.
Fatal
(
"should sudo"
)
}
if
!
strings
.
Contains
(
comm
.
StartCmd
.
Command
,
"gsutil cp"
)
{
t
.
Fatalf
(
"bad command: %#v"
,
comm
.
StartCmd
.
Command
)
}
}
config.go
View file @
4f150dcc
...
...
@@ -24,6 +24,7 @@ const defaultConfig = `
"amazon-instance": "packer-builder-amazon-instance",
"digitalocean": "packer-builder-digitalocean",
"docker": "packer-builder-docker",
"googlecompute": "packer-builder-googlecompute",
"openstack": "packer-builder-openstack",
"qemu": "packer-builder-qemu",
"virtualbox": "packer-builder-virtualbox",
...
...
plugin/builder-googlecompute/main.go
0 → 100644
View file @
4f150dcc
package
main
import
(
"github.com/mitchellh/packer/builder/googlecompute"
"github.com/mitchellh/packer/packer/plugin"
)
func
main
()
{
server
,
err
:=
plugin
.
Server
()
if
err
!=
nil
{
panic
(
err
)
}
server
.
RegisterBuilder
(
new
(
googlecompute
.
Builder
))
server
.
Serve
()
}
plugin/builder-googlecompute/main_test.go
0 → 100644
View file @
4f150dcc
package
main
website/source/docs/builders/googlecompute.markdown
0 → 100644
View file @
4f150dcc
---
layout
:
"
docs"
---
# Google Compute Builder
Type:
`googlecompute`
The
`googlecompute`
builder is able to create
[
images
](
https://developers.google.com/compute/docs/images
)
for use with
[
Google Compute Engine
](
https://cloud.google.com/products/compute-engine
)
(GCE) based on existing images. Google Compute Engine doesn't allow the creation
of images from scratch.
## Setting Up API Access
There is a small setup step required in order to obtain the credentials
that Packer needs to use Google Compute Engine. This needs to be done only
once if you intend to share the credentials.
In order for Packer to talk to Google Compute Engine, it will need
a _client secrets_ JSON file and a _client private key_. Both of these are
obtained from the
[
Google Cloud Console
](
https://cloud.google.com/console
)
.
Follow the steps below:
1.
Log into the
[
Google Cloud Console
](
https://cloud.google.com/console
)
2.
Click on the project you want to use Packer with (or create one if you
don't have one yet).
3.
Click "APIs & auth" in the left sidebar
4.
Click "Registered apps" in the left sidebar
5.
Click "Register App" and register a "Web Application". Choose any
name you'd like.
7.
After creating the app, click "Certificate" (below the OAuth 2.0 Client
ID section), and click "Download JSON". This is your _client secrets JSON_
file. Make sure you didn't download the JSON from the "OAuth 2.0" section!
This is a common mistake and will cause the builder to not work.
8.
Next, click "Generate Certificate". You should be prompted to download
a private key. Note the password for the private key! This private key
is your _client private key_.
Finally, one last step, you'll have to convert the
`p12`
file you
got from Google into the PEM format. You can do this with OpenSSL, which
is installed standard on most Unixes:
```
$ openssl pkcs12 -in <path to .p12> -nocerts -passin pass:notasecret \
-nodes -out private_key.pem
```
The client secrets JSON you downloaded along with the new "private
\_
key.pem"
file are the two files you need to configure Packer with to talk to GCE.
## Basic Example
Below is a fully functioning example. It doesn't do anything useful,
since no provisioners are defined, but it will effectively repackage an
existing GCE image. The client secrets file and private key file are the
files obtained in the previous section.
<pre
class=
"prettyprint"
>
{
"type": "googlecompute",
"bucket_name": "packer-images",
"client_secrets_file": "client_secret.json",
"private_key_file": "XXXXXX-privatekey.p12",
"project_id": "my-project",
"source_image": "debian-7-wheezy-v20131014",
"zone": "us-central1-a"
}
</pre>
## Configuration Reference
Configuration options are organized below into two categories: required and optional. Within
each category, the available options are alphabetized and described.
Required:
*
`bucket_name`
(string) - The Google Cloud Storage bucket to store the
images that are created.
*
`client_secrets_file`
(string) - The client secrets JSON file that
was set up in the section above.
*
`private_key_file`
(string) - The client private key file that was
generated in the section above.
*
`project_id`
(string) - The project ID that will be used to launch instances
and store images.
*
`source_image`
(string) - The source image to use to create the new image
from. Example: "debian-7"
*
`zone`
(string) - The zone in which to launch the instance used to create
the image. Example: "us-central1-a"
Optional:
*
`image_name`
(string) - The unique name of the resulting image.
Defaults to
`packer-{{timestamp}}`
.
*
`image_description`
(string) - The description of the resulting image.
*
`machine_type`
(string) - The machine type. Defaults to
`n1-standard-1`
.
*
`network`
(string) - The Google Compute network to use for the launched
instance. Defaults to
`default`
.
*
`passphrase`
(string) - The passphrase to use if the
`private_key_file`
is encrypted.
*
`ssh_port`
(int) - The SSH port. Defaults to 22.
*
`ssh_timeout`
(string) - The time to wait for SSH to become available.
Defaults to "1m".
*
`ssh_username`
(string) - The SSH username. Defaults to "root".
*
`state_timeout`
(string) - The time to wait for instance state changes.
Defaults to "5m".
## Gotchas
Centos images have root ssh access disabled by default. Set
`ssh_username`
to any user, which will be created by packer with sudo access.
The machine type must have a scratch disk, which means you can't use an
`f1-micro`
or
`g1-small`
to build images.
website/source/layouts/docs.erb
View file @
4f150dcc
...
...
@@ -33,6 +33,7 @@
<li><a
href=
"/docs/builders/amazon.html"
>
Amazon EC2 (AMI)
</a></li>
<li><a
href=
"/docs/builders/digitalocean.html"
>
DigitalOcean
</a></li>
<li><a
href=
"/docs/builders/docker.html"
>
Docker
</a></li>
<li><a
href=
"/docs/builders/googlecompute.html"
>
Google Compute Engine
</a></li>
<li><a
href=
"/docs/builders/openstack.html"
>
OpenStack
</a></li>
<li><a
href=
"/docs/builders/qemu.html"
>
QEMU
</a></li>
<li><a
href=
"/docs/builders/virtualbox.html"
>
VirtualBox
</a></li>
...
...
website/source/stylesheets/_components.scss
View file @
4f150dcc
...
...
@@ -174,7 +174,6 @@ header .header {
ul
,
ol
{
list-style-type
:
circle
;
list-style-position
:
inside
;
margin-top
:
$baseline
;
margin-left
:
20px
;
...
...
@@ -188,6 +187,14 @@ header .header {
}
}
ul
{
list-style-type
:
circle
;
}
ol
{
list-style-type
:
decimal
;
}
div
.alert
{
font-family
:
$serif
;
font-size
:
17px
;
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment