diff --git a/builder/googlecompute/config.go b/builder/googlecompute/config.go index cdf4581e5..7f4325a20 100644 --- a/builder/googlecompute/config.go +++ b/builder/googlecompute/config.go @@ -287,6 +287,11 @@ type Config struct { VaultGCPOauthEngine string `mapstructure:"vault_gcp_oauth_engine"` // The zone in which to launch the instance used to create the image. // Example: "us-central1-a" + + // The time to wait between instance creation and adding SSH keys. + // Example value: `5m`. + WaitToAddSSHKeys time.Duration `mapstructure:"wait_to_add_ssh_keys"` + Zone string `mapstructure:"zone" required:"true"` account *ServiceAccount diff --git a/builder/googlecompute/config.hcl2spec.go b/builder/googlecompute/config.hcl2spec.go index c235087f0..6c98d8a2a 100644 --- a/builder/googlecompute/config.hcl2spec.go +++ b/builder/googlecompute/config.hcl2spec.go @@ -117,6 +117,7 @@ type FlatConfig struct { UseInternalIP *bool `mapstructure:"use_internal_ip" required:"false" cty:"use_internal_ip" hcl:"use_internal_ip"` UseOSLogin *bool `mapstructure:"use_os_login" required:"false" cty:"use_os_login" hcl:"use_os_login"` VaultGCPOauthEngine *string `mapstructure:"vault_gcp_oauth_engine" cty:"vault_gcp_oauth_engine" hcl:"vault_gcp_oauth_engine"` + WaitToAddSSHKeys *string `mapstructure:"wait_to_add_ssh_keys" cty:"wait_to_add_ssh_keys" hcl:"wait_to_add_ssh_keys"` Zone *string `mapstructure:"zone" required:"true" cty:"zone" hcl:"zone"` } @@ -240,6 +241,7 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec { "use_internal_ip": &hcldec.AttrSpec{Name: "use_internal_ip", Type: cty.Bool, Required: false}, "use_os_login": &hcldec.AttrSpec{Name: "use_os_login", Type: cty.Bool, Required: false}, "vault_gcp_oauth_engine": &hcldec.AttrSpec{Name: "vault_gcp_oauth_engine", Type: cty.String, Required: false}, + "wait_to_add_ssh_keys": &hcldec.AttrSpec{Name: "wait_to_add_ssh_keys", Type: cty.String, Required: false}, "zone": &hcldec.AttrSpec{Name: "zone", Type: cty.String, Required: false}, } return s diff --git a/builder/googlecompute/config_test.go b/builder/googlecompute/config_test.go index dab12c9f9..7e73ec14d 100644 --- a/builder/googlecompute/config_test.go +++ b/builder/googlecompute/config_test.go @@ -84,6 +84,17 @@ func TestConfigPrepare(t *testing.T) { false, }, + { + "wait_to_add_ssh_keys", + "SO BAD", + true, + }, + { + "wait_to_add_ssh_keys", + "5s", + false, + }, + { "state_timeout", "SO BAD", diff --git a/builder/googlecompute/step_create_instance.go b/builder/googlecompute/step_create_instance.go index 371fee64a..9e9eb5094 100644 --- a/builder/googlecompute/step_create_instance.go +++ b/builder/googlecompute/step_create_instance.go @@ -85,6 +85,43 @@ func (c *Config) createInstanceMetadata(sourceImage *Image, sshPublicKey string) return instanceMetadata, nil } +func (s *StepCreateInstance) addMetadataToInstance(ctx context.Context, errCh chan<- error, name, state multistep.StateBag, metadata map[string]string) { + + c := state.Get("config").(*Config) + d := state.Get("driver").(Driver) + + instance, err := d.service.Instances.Get(d.projectId, c.Zone, name).Do() + if err != nil { + errCh <- err + return + } + instance.Metadata.Items = append(instance.Metadata.Items, &compute.MetadataItems{Key: "windows-keys", Value: &sshPublicKey}) + + op, err := d.service.Instances.SetMetadata(d.projectId, zone, name, &compute.Metadata{ + Fingerprint: instance.Metadata.Fingerprint, + Items: instance.Metadata.Items, + }).Do() + + if err != nil { + errCh <- err + return + } + + newErrCh := make(chan error, 1) + go waitForState(newErrCh, "DONE", d.refreshZoneOp(zone, op)) + + select { + case err = <-newErrCh: + case <-time.After(time.Second * 30): + err = errors.New("time out while waiting for SSH public key to be added to instance") + } + + if err != nil { + errCh <- err + return + } +} + func getImage(c *Config, d Driver) (*Image, error) { name := c.SourceImageFamily fromFamily := true @@ -139,6 +176,11 @@ func (s *StepCreateInstance) Run(ctx context.Context, state multistep.StateBag) return multistep.ActionHalt } + if c.WaitToAddSSHKeys == 0 { + ui.Message("Adding SSH keys during instance creation...") + metadata = make(map[string]string) + } + errCh, err = d.RunInstance(&InstanceConfig{ AcceleratorType: c.AcceleratorType, AcceleratorCount: c.AcceleratorCount, @@ -199,9 +241,36 @@ func (s *StepCreateInstance) Run(ctx context.Context, state multistep.StateBag) // instance id inside of the provisioners, used in step_provision. state.Put("instance_id", name) + if c.WaitToAddSSHKeys > 0 { + ui.Say(fmt.Sprintf("Waiting %s before adding SSH keys...", + c.WaitToAddSSHKeys.String())) + cancelled := s.wait(c.WaitToAddSSHKeys, ctx) + if cancelled { + return multistep.ActionHalt + } + + metadata, errs = s.addMetadataToInstance(metadata, d, c) + if errs != nil { + state.Put("error", errs.Error()) + ui.Error(errs.Error()) + return multistep.ActionHalt + } + } + return multistep.ActionContinue } +func (s *StepCreateInstance) wait(waitLen time.Duration, ctx context.Context) bool { + // Use a select to determine if we get cancelled during the wait + select { + case <-ctx.Done(): + return true + case <-time.After(waitLen): + } + ui.Message("Wait over; adding SSH keys to instance...") + return false +} + // Cleanup destroys the GCE instance created during the image creation process. func (s *StepCreateInstance) Cleanup(state multistep.StateBag) { nameRaw, ok := state.GetOk("instance_name")