Merge pull request #1464 from mitchellh/f-new-gce
builder/googlecompute: uses new auth style
This commit is contained in:
commit
7002b8f07e
|
@ -0,0 +1,35 @@
|
||||||
|
package googlecompute
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
// accountFile represents the structure of the account file JSON file.
|
||||||
|
type accountFile struct {
|
||||||
|
PrivateKeyId string `json:"private_key_id"`
|
||||||
|
PrivateKey string `json:"private_key"`
|
||||||
|
ClientEmail string `json:"client_email"`
|
||||||
|
ClientId string `json:"client_id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// clientSecretsFile represents the structure of the client secrets JSON file.
|
||||||
|
type clientSecretsFile struct {
|
||||||
|
Web struct {
|
||||||
|
AuthURI string `json:"auth_uri"`
|
||||||
|
ClientEmail string `json:"client_email"`
|
||||||
|
ClientId string `json:"client_id"`
|
||||||
|
TokenURI string `json:"token_uri"`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func loadJSON(result interface{}, path string) error {
|
||||||
|
f, err := os.Open(path)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
|
dec := json.NewDecoder(f)
|
||||||
|
return dec.Decode(result)
|
||||||
|
}
|
|
@ -35,7 +35,7 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
|
||||||
// representing a GCE machine image.
|
// representing a GCE machine image.
|
||||||
func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packer.Artifact, error) {
|
func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packer.Artifact, error) {
|
||||||
driver, err := NewDriverGCE(
|
driver, err := NewDriverGCE(
|
||||||
ui, b.config.ProjectId, b.config.clientSecrets, b.config.privateKeyBytes)
|
ui, b.config.ProjectId, &b.config.account, &b.config.clientSecrets)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,32 +0,0 @@
|
||||||
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
|
|
||||||
}
|
|
|
@ -1,31 +0,0 @@
|
||||||
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"}}`
|
|
|
@ -16,8 +16,11 @@ import (
|
||||||
type Config struct {
|
type Config struct {
|
||||||
common.PackerConfig `mapstructure:",squash"`
|
common.PackerConfig `mapstructure:",squash"`
|
||||||
|
|
||||||
BucketName string `mapstructure:"bucket_name"`
|
AccountFile string `mapstructure:"account_file"`
|
||||||
ClientSecretsFile string `mapstructure:"client_secrets_file"`
|
ClientSecretsFile string `mapstructure:"client_secrets_file"`
|
||||||
|
ProjectId string `mapstructure:"project_id"`
|
||||||
|
|
||||||
|
BucketName string `mapstructure:"bucket_name"`
|
||||||
DiskSizeGb int64 `mapstructure:"disk_size"`
|
DiskSizeGb int64 `mapstructure:"disk_size"`
|
||||||
ImageName string `mapstructure:"image_name"`
|
ImageName string `mapstructure:"image_name"`
|
||||||
ImageDescription string `mapstructure:"image_description"`
|
ImageDescription string `mapstructure:"image_description"`
|
||||||
|
@ -25,9 +28,6 @@ type Config struct {
|
||||||
MachineType string `mapstructure:"machine_type"`
|
MachineType string `mapstructure:"machine_type"`
|
||||||
Metadata map[string]string `mapstructure:"metadata"`
|
Metadata map[string]string `mapstructure:"metadata"`
|
||||||
Network string `mapstructure:"network"`
|
Network string `mapstructure:"network"`
|
||||||
Passphrase string `mapstructure:"passphrase"`
|
|
||||||
PrivateKeyFile string `mapstructure:"private_key_file"`
|
|
||||||
ProjectId string `mapstructure:"project_id"`
|
|
||||||
SourceImage string `mapstructure:"source_image"`
|
SourceImage string `mapstructure:"source_image"`
|
||||||
SourceImageProjectId string `mapstructure:"source_image_project_id"`
|
SourceImageProjectId string `mapstructure:"source_image_project_id"`
|
||||||
SSHUsername string `mapstructure:"ssh_username"`
|
SSHUsername string `mapstructure:"ssh_username"`
|
||||||
|
@ -37,7 +37,8 @@ type Config struct {
|
||||||
Tags []string `mapstructure:"tags"`
|
Tags []string `mapstructure:"tags"`
|
||||||
Zone string `mapstructure:"zone"`
|
Zone string `mapstructure:"zone"`
|
||||||
|
|
||||||
clientSecrets *clientSecrets
|
account accountFile
|
||||||
|
clientSecrets clientSecretsFile
|
||||||
instanceName string
|
instanceName string
|
||||||
privateKeyBytes []byte
|
privateKeyBytes []byte
|
||||||
sshTimeout time.Duration
|
sshTimeout time.Duration
|
||||||
|
@ -104,15 +105,15 @@ func NewConfig(raws ...interface{}) (*Config, []string, error) {
|
||||||
|
|
||||||
// Process Templates
|
// Process Templates
|
||||||
templates := map[string]*string{
|
templates := map[string]*string{
|
||||||
"bucket_name": &c.BucketName,
|
"account_file": &c.AccountFile,
|
||||||
"client_secrets_file": &c.ClientSecretsFile,
|
"client_secrets_file": &c.ClientSecretsFile,
|
||||||
|
|
||||||
|
"bucket_name": &c.BucketName,
|
||||||
"image_name": &c.ImageName,
|
"image_name": &c.ImageName,
|
||||||
"image_description": &c.ImageDescription,
|
"image_description": &c.ImageDescription,
|
||||||
"instance_name": &c.InstanceName,
|
"instance_name": &c.InstanceName,
|
||||||
"machine_type": &c.MachineType,
|
"machine_type": &c.MachineType,
|
||||||
"network": &c.Network,
|
"network": &c.Network,
|
||||||
"passphrase": &c.Passphrase,
|
|
||||||
"private_key_file": &c.PrivateKeyFile,
|
|
||||||
"project_id": &c.ProjectId,
|
"project_id": &c.ProjectId,
|
||||||
"source_image": &c.SourceImage,
|
"source_image": &c.SourceImage,
|
||||||
"source_image_project_id": &c.SourceImageProjectId,
|
"source_image_project_id": &c.SourceImageProjectId,
|
||||||
|
@ -137,16 +138,16 @@ func NewConfig(raws ...interface{}) (*Config, []string, error) {
|
||||||
errs, errors.New("a bucket_name must be specified"))
|
errs, errors.New("a bucket_name must be specified"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if c.AccountFile == "" {
|
||||||
|
errs = packer.MultiErrorAppend(
|
||||||
|
errs, errors.New("an account_file must be specified"))
|
||||||
|
}
|
||||||
|
|
||||||
if c.ClientSecretsFile == "" {
|
if c.ClientSecretsFile == "" {
|
||||||
errs = packer.MultiErrorAppend(
|
errs = packer.MultiErrorAppend(
|
||||||
errs, errors.New("a client_secrets_file must be specified"))
|
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 == "" {
|
if c.ProjectId == "" {
|
||||||
errs = packer.MultiErrorAppend(
|
errs = packer.MultiErrorAppend(
|
||||||
errs, errors.New("a project_id must be specified"))
|
errs, errors.New("a project_id must be specified"))
|
||||||
|
@ -177,22 +178,17 @@ func NewConfig(raws ...interface{}) (*Config, []string, error) {
|
||||||
}
|
}
|
||||||
c.stateTimeout = stateTimeout
|
c.stateTimeout = stateTimeout
|
||||||
|
|
||||||
if c.ClientSecretsFile != "" {
|
if c.AccountFile != "" {
|
||||||
// Load the client secrets file.
|
if err := loadJSON(&c.account, c.AccountFile); err != nil {
|
||||||
cs, err := loadClientSecrets(c.ClientSecretsFile)
|
|
||||||
if err != nil {
|
|
||||||
errs = packer.MultiErrorAppend(
|
errs = packer.MultiErrorAppend(
|
||||||
errs, fmt.Errorf("Failed parsing client secrets file: %s", err))
|
errs, fmt.Errorf("Failed parsing account file: %s", err))
|
||||||
}
|
}
|
||||||
c.clientSecrets = cs
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if c.PrivateKeyFile != "" {
|
if c.ClientSecretsFile != "" {
|
||||||
// Load the private key.
|
if err := loadJSON(&c.clientSecrets, c.ClientSecretsFile); err != nil {
|
||||||
c.privateKeyBytes, err = processPrivateKeyFile(c.PrivateKeyFile, c.Passphrase)
|
|
||||||
if err != nil {
|
|
||||||
errs = packer.MultiErrorAppend(
|
errs = packer.MultiErrorAppend(
|
||||||
errs, fmt.Errorf("Failed loading private key file: %s", err))
|
errs, fmt.Errorf("Failed parsing client secrets file: %s", err))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,14 +1,15 @@
|
||||||
package googlecompute
|
package googlecompute
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"io/ioutil"
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
func testConfig(t *testing.T) map[string]interface{} {
|
func testConfig(t *testing.T) map[string]interface{} {
|
||||||
return map[string]interface{}{
|
return map[string]interface{}{
|
||||||
|
"account_file": testAccountFile(t),
|
||||||
"bucket_name": "foo",
|
"bucket_name": "foo",
|
||||||
"client_secrets_file": testClientSecretsFile(t),
|
"client_secrets_file": testClientSecretsFile(t),
|
||||||
"private_key_file": testPrivateKeyFile(t),
|
|
||||||
"project_id": "hashicorp",
|
"project_id": "hashicorp",
|
||||||
"source_image": "foo",
|
"source_image": "foo",
|
||||||
"zone": "us-east-1a",
|
"zone": "us-east-1a",
|
||||||
|
@ -84,16 +85,6 @@ func TestConfigPrepare(t *testing.T) {
|
||||||
true,
|
true,
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
|
||||||
"private_key_file",
|
|
||||||
nil,
|
|
||||||
true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"private_key_file",
|
|
||||||
testPrivateKeyFile(t),
|
|
||||||
false,
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"private_key_file",
|
"private_key_file",
|
||||||
"/tmp/i/should/not/exist",
|
"/tmp/i/should/not/exist",
|
||||||
|
@ -174,3 +165,37 @@ func TestConfigPrepare(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func testAccountFile(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(testAccountContent)); err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return tf.Name()
|
||||||
|
}
|
||||||
|
|
||||||
|
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()
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is just some dummy data that doesn't actually work (it was revoked
|
||||||
|
// a long time ago).
|
||||||
|
const testAccountContent = `{}`
|
||||||
|
|
||||||
|
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"}}`
|
||||||
|
|
|
@ -23,22 +23,27 @@ type driverGCE struct {
|
||||||
const DriverScopes string = "https://www.googleapis.com/auth/compute " +
|
const DriverScopes string = "https://www.googleapis.com/auth/compute " +
|
||||||
"https://www.googleapis.com/auth/devstorage.full_control"
|
"https://www.googleapis.com/auth/devstorage.full_control"
|
||||||
|
|
||||||
func NewDriverGCE(ui packer.Ui, projectId string, c *clientSecrets, key []byte) (Driver, error) {
|
func NewDriverGCE(ui packer.Ui, p string, a *accountFile, c *clientSecretsFile) (Driver, error) {
|
||||||
log.Printf("[INFO] Requesting token...")
|
// Get the token for use in our requests
|
||||||
log.Printf("[INFO] -- Email: %s", c.Web.ClientEmail)
|
log.Printf("[INFO] Requesting Google token...")
|
||||||
|
log.Printf("[INFO] -- Email: %s", a.ClientEmail)
|
||||||
log.Printf("[INFO] -- Scopes: %s", DriverScopes)
|
log.Printf("[INFO] -- Scopes: %s", DriverScopes)
|
||||||
log.Printf("[INFO] -- Private Key Length: %d", len(key))
|
log.Printf("[INFO] -- Private Key Length: %d", len(a.PrivateKey))
|
||||||
log.Printf("[INFO] -- Token URL: %s", c.Web.TokenURI)
|
log.Printf("[INFO] -- Token URL: %s", c.Web.TokenURI)
|
||||||
jwtTok := jwt.NewToken(c.Web.ClientEmail, DriverScopes, key)
|
jwtTok := jwt.NewToken(
|
||||||
|
a.ClientEmail,
|
||||||
|
DriverScopes,
|
||||||
|
[]byte(a.PrivateKey))
|
||||||
jwtTok.ClaimSet.Aud = c.Web.TokenURI
|
jwtTok.ClaimSet.Aud = c.Web.TokenURI
|
||||||
token, err := jwtTok.Assert(new(http.Client))
|
token, err := jwtTok.Assert(new(http.Client))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, fmt.Errorf("Error retrieving auth token: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Instantiate the transport to communicate to Google
|
||||||
transport := &oauth.Transport{
|
transport := &oauth.Transport{
|
||||||
Config: &oauth.Config{
|
Config: &oauth.Config{
|
||||||
ClientId: c.Web.ClientId,
|
ClientId: a.ClientId,
|
||||||
Scope: DriverScopes,
|
Scope: DriverScopes,
|
||||||
TokenURL: c.Web.TokenURI,
|
TokenURL: c.Web.TokenURI,
|
||||||
AuthURL: c.Web.AuthURI,
|
AuthURL: c.Web.AuthURI,
|
||||||
|
@ -46,14 +51,14 @@ func NewDriverGCE(ui packer.Ui, projectId string, c *clientSecrets, key []byte)
|
||||||
Token: token,
|
Token: token,
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Printf("[INFO] Instantiating client...")
|
log.Printf("[INFO] Instantiating GCE client...")
|
||||||
service, err := compute.New(transport.Client())
|
service, err := compute.New(transport.Client())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return &driverGCE{
|
return &driverGCE{
|
||||||
projectId: projectId,
|
projectId: p,
|
||||||
service: service,
|
service: service,
|
||||||
ui: ui,
|
ui: ui,
|
||||||
}, nil
|
}, nil
|
||||||
|
|
|
@ -13,38 +13,27 @@ for use with [Google Compute Engine](https://cloud.google.com/products/compute-e
|
||||||
(GCE) based on existing images. Google Compute Engine doesn't allow the creation
|
(GCE) based on existing images. Google Compute Engine doesn't allow the creation
|
||||||
of images from scratch.
|
of images from scratch.
|
||||||
|
|
||||||
## Setting Up API Access
|
## Authentication
|
||||||
|
|
||||||
There is a small setup step required in order to obtain the credentials
|
Authenticating with Google Cloud services requires two separate JSON
|
||||||
that Packer needs to use Google Compute Engine. This needs to be done only
|
files: one which we call the _account file_ and the _client secrets file_.
|
||||||
once if you intend to share the credentials.
|
|
||||||
|
|
||||||
In order for Packer to talk to Google Compute Engine, it will need
|
Both of these files are downloaded directly from the
|
||||||
a _client secrets_ JSON file and a _client private key_. Both of these are
|
[Google Developers Console](https://console.developers.google.com). To make
|
||||||
obtained from the [Google Cloud Console](https://cloud.google.com/console).
|
the process more straightforwarded, it is documented here.
|
||||||
|
|
||||||
Follow the steps below:
|
1. Log into the [Google Developers Console](https://console.developers.google.com)
|
||||||
|
and select a project.
|
||||||
|
|
||||||
1. Log into the [Google Cloud Console](https://cloud.google.com/console)
|
2. Under the "APIs & Auth" section, click "Credentials."
|
||||||
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 "Credentials" in the left sidebar
|
|
||||||
5. Click "Create New Client ID" and choose "Service Account"
|
|
||||||
6. A private key will be downloaded for you. Note the password for the private key! This private key is your _client private key_.
|
|
||||||
7. After creating the account, 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.
|
|
||||||
|
|
||||||
Finally, one last step, you'll have to convert the `p12` file you
|
3. Click the "Download JSON" button under the "Compute Engine and App Engine"
|
||||||
got from Google into the PEM format. You can do this with OpenSSL, which
|
account in the OAuth section. The file should start with "client\_secrets".
|
||||||
is installed standard on most Unixes:
|
This is your _client secrets file_.
|
||||||
|
|
||||||
```
|
4. Create a new OAuth client ID and select "Service Account" as the type
|
||||||
$ openssl pkcs12 -in <path to .p12> -nocerts -passin pass:notasecret \
|
of account. Once created, a JSON file should be downloaded. This is your
|
||||||
-nodes -out private_key.pem
|
_account file_.
|
||||||
```
|
|
||||||
|
|
||||||
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
|
## Basic Example
|
||||||
|
|
||||||
|
@ -57,8 +46,8 @@ files obtained in the previous section.
|
||||||
{
|
{
|
||||||
"type": "googlecompute",
|
"type": "googlecompute",
|
||||||
"bucket_name": "my-project-packer-images",
|
"bucket_name": "my-project-packer-images",
|
||||||
|
"account_file": "account.json",
|
||||||
"client_secrets_file": "client_secret.json",
|
"client_secrets_file": "client_secret.json",
|
||||||
"private_key_file": "XXXXXX-privatekey.p12",
|
|
||||||
"project_id": "my-project",
|
"project_id": "my-project",
|
||||||
"source_image": "debian-7-wheezy-v20140718",
|
"source_image": "debian-7-wheezy-v20140718",
|
||||||
"zone": "us-central1-a"
|
"zone": "us-central1-a"
|
||||||
|
@ -72,6 +61,9 @@ each category, the available options are alphabetized and described.
|
||||||
|
|
||||||
### Required:
|
### Required:
|
||||||
|
|
||||||
|
* `account_file` (string) - The JSON file containing your account credentials.
|
||||||
|
Instructions for how to retrieve these are above.
|
||||||
|
|
||||||
* `bucket_name` (string) - The Google Cloud Storage bucket to store the
|
* `bucket_name` (string) - The Google Cloud Storage bucket to store the
|
||||||
images that are created. The bucket must already exist in your project.
|
images that are created. The bucket must already exist in your project.
|
||||||
|
|
||||||
|
@ -113,9 +105,6 @@ each category, the available options are alphabetized and described.
|
||||||
* `network` (string) - The Google Compute network to use for the launched
|
* `network` (string) - The Google Compute network to use for the launched
|
||||||
instance. Defaults to `default`.
|
instance. Defaults to `default`.
|
||||||
|
|
||||||
* `passphrase` (string) - The passphrase to use if the `private_key_file`
|
|
||||||
is encrypted.
|
|
||||||
|
|
||||||
* `ssh_port` (integer) - The SSH port. Defaults to 22.
|
* `ssh_port` (integer) - The SSH port. Defaults to 22.
|
||||||
|
|
||||||
* `ssh_timeout` (string) - The time to wait for SSH to become available.
|
* `ssh_timeout` (string) - The time to wait for SSH to become available.
|
||||||
|
|
Loading…
Reference in New Issue