Merge pull request #9505 from hashicorp/f-googlecompute-wrap_startup_script-option
builder/googlecompute: Add WrapStartupScriptFile configuration option
This commit is contained in:
commit
eeb6f017bd
|
@ -192,9 +192,22 @@ type Config struct {
|
|||
// A list of project IDs to search for the source image. Packer will search the first
|
||||
// project ID in the list first, and fall back to the next in the list, until it finds the source image.
|
||||
SourceImageProjectId []string `mapstructure:"source_image_project_id" required:"false"`
|
||||
// The path to a startup script to run on the VM from which the image will
|
||||
// be made.
|
||||
// The path to a startup script to run on the launched instance from which the image will
|
||||
// be made. When set, the contents of the startup script file will be added to the instance metadata
|
||||
// under the `"startup_script"` metadata property. See [Providing startup script contents directly](https://cloud.google.com/compute/docs/startupscript#providing_startup_script_contents_directly) for more details.
|
||||
//
|
||||
// When using `startup_script_file` the following rules apply:
|
||||
// - The contents of the script file will overwrite the value of the `"startup_script"` metadata property at runtime.
|
||||
// - The contents of the script file will be wrapped in Packer's startup script wrapper, unless `wrap_startup_script` is disabled. See `wrap_startup_script` for more details.
|
||||
// - Not supported by Windows instances. See [Startup Scripts for Windows](https://cloud.google.com/compute/docs/startupscript#providing_a_startup_script_for_windows_instances) for more details.
|
||||
StartupScriptFile string `mapstructure:"startup_script_file" required:"false"`
|
||||
// For backwards compatibility this option defaults to `"true"` in the future it will default to `"false"`.
|
||||
// If "true", the contents of `startup_script_file` or `"startup_script"` in the instance metadata
|
||||
// is wrapped in a Packer specific script that tracks the execution and completion of the provided
|
||||
// startup script. The wrapper ensures that the builder will not continue until the startup script has been executed.
|
||||
// - The use of the wrapped script file requires that the user or service account
|
||||
// running the build has the compute.instance.Metadata role.
|
||||
WrapStartupScriptFile config.Trilean `mapstructure:"wrap_startup_script" required:"false"`
|
||||
// The Google Compute subnetwork id or URL to use for the launched
|
||||
// instance. Only required if the network has been created with custom
|
||||
// subnetting. Note, the region of the subnetwork must match the region or
|
||||
|
@ -448,6 +461,10 @@ func (c *Config) Prepare(raws ...interface{}) ([]string, error) {
|
|||
errs = packer.MultiErrorAppend(
|
||||
errs, fmt.Errorf("startup_script_file: %v", err))
|
||||
}
|
||||
|
||||
if c.WrapStartupScriptFile == config.TriUnset {
|
||||
c.WrapStartupScriptFile = config.TriTrue
|
||||
}
|
||||
}
|
||||
|
||||
// Check for any errors.
|
||||
|
|
|
@ -102,6 +102,7 @@ type FlatConfig struct {
|
|||
SourceImageFamily *string `mapstructure:"source_image_family" required:"true" cty:"source_image_family" hcl:"source_image_family"`
|
||||
SourceImageProjectId []string `mapstructure:"source_image_project_id" required:"false" cty:"source_image_project_id" hcl:"source_image_project_id"`
|
||||
StartupScriptFile *string `mapstructure:"startup_script_file" required:"false" cty:"startup_script_file" hcl:"startup_script_file"`
|
||||
WrapStartupScriptFile *bool `mapstructure:"wrap_startup_script" required:"false" cty:"wrap_startup_script" hcl:"wrap_startup_script"`
|
||||
Subnetwork *string `mapstructure:"subnetwork" required:"false" cty:"subnetwork" hcl:"subnetwork"`
|
||||
Tags []string `mapstructure:"tags" required:"false" cty:"tags" hcl:"tags"`
|
||||
UseInternalIP *bool `mapstructure:"use_internal_ip" required:"false" cty:"use_internal_ip" hcl:"use_internal_ip"`
|
||||
|
@ -214,6 +215,7 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
|
|||
"source_image_family": &hcldec.AttrSpec{Name: "source_image_family", Type: cty.String, Required: false},
|
||||
"source_image_project_id": &hcldec.AttrSpec{Name: "source_image_project_id", Type: cty.List(cty.String), Required: false},
|
||||
"startup_script_file": &hcldec.AttrSpec{Name: "startup_script_file", Type: cty.String, Required: false},
|
||||
"wrap_startup_script": &hcldec.AttrSpec{Name: "wrap_startup_script", Type: cty.Bool, Required: false},
|
||||
"subnetwork": &hcldec.AttrSpec{Name: "subnetwork", Type: cty.String, Required: false},
|
||||
"tags": &hcldec.AttrSpec{Name: "tags", Type: cty.List(cty.String), Required: false},
|
||||
"use_internal_ip": &hcldec.AttrSpec{Name: "use_internal_ip", Type: cty.Bool, Required: false},
|
||||
|
|
|
@ -40,19 +40,30 @@ func (c *Config) createInstanceMetadata(sourceImage *Image, sshPublicKey string)
|
|||
instanceMetadata[sshMetaKey] = sshKeys
|
||||
}
|
||||
|
||||
// Wrap any startup script with our own startup script.
|
||||
startupScript := instanceMetadata[StartupScriptKey]
|
||||
if c.StartupScriptFile != "" {
|
||||
var content []byte
|
||||
content, err = ioutil.ReadFile(c.StartupScriptFile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
instanceMetadata[StartupWrappedScriptKey] = string(content)
|
||||
} else if wrappedStartupScript, exists := instanceMetadata[StartupScriptKey]; exists {
|
||||
instanceMetadata[StartupWrappedScriptKey] = wrappedStartupScript
|
||||
startupScript = string(content)
|
||||
}
|
||||
instanceMetadata[StartupScriptKey] = startupScript
|
||||
|
||||
// Wrap any found startup script with our own startup script wrapper.
|
||||
if startupScript != "" && c.WrapStartupScriptFile.True() {
|
||||
instanceMetadata[StartupScriptKey] = StartupScriptLinux
|
||||
instanceMetadata[StartupWrappedScriptKey] = startupScript
|
||||
instanceMetadata[StartupScriptStatusKey] = StartupScriptStatusNotDone
|
||||
}
|
||||
|
||||
if sourceImage.IsWindows() {
|
||||
// Windows startup script support is not yet implemented so clear any script data and set status to done
|
||||
instanceMetadata[StartupScriptKey] = StartupScriptWindows
|
||||
instanceMetadata[StartupScriptStatusKey] = StartupScriptStatusDone
|
||||
}
|
||||
|
||||
// Read metadata from files specified with metadata_files
|
||||
for key, value := range c.MetadataFiles {
|
||||
var content []byte
|
||||
content, err = ioutil.ReadFile(value)
|
||||
|
@ -62,16 +73,6 @@ func (c *Config) createInstanceMetadata(sourceImage *Image, sshPublicKey string)
|
|||
instanceMetadata[key] = string(content)
|
||||
}
|
||||
|
||||
if sourceImage.IsWindows() {
|
||||
// Windows startup script support is not yet implemented.
|
||||
// Mark the startup script as done.
|
||||
instanceMetadata[StartupScriptKey] = StartupScriptWindows
|
||||
instanceMetadata[StartupScriptStatusKey] = StartupScriptStatusDone
|
||||
} else {
|
||||
instanceMetadata[StartupScriptKey] = StartupScriptLinux
|
||||
instanceMetadata[StartupScriptStatusKey] = StartupScriptStatusNotDone
|
||||
}
|
||||
|
||||
if errs != nil && len(errs.Errors) > 0 {
|
||||
return instanceMetadata, errs
|
||||
}
|
||||
|
|
|
@ -3,10 +3,12 @@ package googlecompute
|
|||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/hashicorp/packer/helper/config"
|
||||
"github.com/hashicorp/packer/helper/multistep"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
@ -342,3 +344,44 @@ func TestCreateInstanceMetadata_metadataFile(t *testing.T) {
|
|||
// ensure the user-data key in metadata is updated with file content
|
||||
assert.Equal(t, metadata["user-data"], content, "user-data field of the instance metadata should have been updated.")
|
||||
}
|
||||
|
||||
func TestCreateInstanceMetadata_withWrapStartupScript(t *testing.T) {
|
||||
tt := []struct {
|
||||
WrapStartupScript config.Trilean
|
||||
StartupScriptContents string
|
||||
WrappedStartupScriptContents string
|
||||
WrappedStartupScriptStatus string
|
||||
}{
|
||||
{
|
||||
WrapStartupScript: config.TriUnset,
|
||||
StartupScriptContents: testMetadataFileContent,
|
||||
},
|
||||
{
|
||||
WrapStartupScript: config.TriFalse,
|
||||
StartupScriptContents: testMetadataFileContent,
|
||||
},
|
||||
{
|
||||
WrapStartupScript: config.TriTrue,
|
||||
StartupScriptContents: StartupScriptLinux,
|
||||
WrappedStartupScriptContents: testMetadataFileContent,
|
||||
WrappedStartupScriptStatus: StartupScriptStatusNotDone,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tt {
|
||||
tc := tc
|
||||
state := testState(t)
|
||||
image := StubImage("test-image", "test-project", []string{}, 100)
|
||||
c := state.Get("config").(*Config)
|
||||
c.StartupScriptFile = testMetadataFile(t)
|
||||
c.WrapStartupScriptFile = tc.WrapStartupScript
|
||||
|
||||
// create our metadata
|
||||
metadata, err := c.createInstanceMetadata(image, "")
|
||||
|
||||
assert.True(t, err == nil, "Metadata creation should have succeeded.")
|
||||
assert.Equal(t, tc.StartupScriptContents, metadata[StartupScriptKey], fmt.Sprintf("Instance metadata for startup script should be %q.", tc.StartupScriptContents))
|
||||
assert.Equal(t, tc.WrappedStartupScriptContents, metadata[StartupWrappedScriptKey], fmt.Sprintf("Instance metadata for wrapped startup script should be %q.", tc.WrappedStartupScriptContents))
|
||||
assert.Equal(t, tc.WrappedStartupScriptStatus, metadata[StartupScriptStatusKey], fmt.Sprintf("Instance metadata startup script status should be %q.", tc.WrappedStartupScriptStatus))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,8 +21,11 @@ func (s *StepWaitStartupScript) Run(ctx context.Context, state multistep.StateBa
|
|||
ui := state.Get("ui").(packer.Ui)
|
||||
instanceName := state.Get("instance_name").(string)
|
||||
|
||||
ui.Say("Waiting for any running startup script to finish...")
|
||||
if config.WrapStartupScriptFile.False() {
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
ui.Say("Waiting for any running startup script to finish...")
|
||||
// Keep checking the serial port output to see if the startup script is done.
|
||||
err := retry.Config{
|
||||
ShouldRetry: func(error) bool {
|
||||
|
|
|
@ -4,6 +4,7 @@ import (
|
|||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/packer/helper/config"
|
||||
"github.com/hashicorp/packer/helper/multistep"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
@ -30,3 +31,36 @@ func TestStepWaitStartupScript(t *testing.T) {
|
|||
assert.Equal(t, d.GetInstanceMetadataZone, testZone, "Incorrect zone passed to GetInstanceMetadata.")
|
||||
assert.Equal(t, d.GetInstanceMetadataName, testInstanceName, "Incorrect instance name passed to GetInstanceMetadata.")
|
||||
}
|
||||
|
||||
func TestStepWaitStartupScript_withWrapStartupScript(t *testing.T) {
|
||||
tt := []struct {
|
||||
WrapStartup config.Trilean
|
||||
Result, Zone, MetadataName string
|
||||
}{
|
||||
{WrapStartup: config.TriTrue, Result: StartupScriptStatusDone, Zone: "test-zone", MetadataName: "test-instance-name"},
|
||||
{WrapStartup: config.TriFalse},
|
||||
}
|
||||
|
||||
for _, tc := range tt {
|
||||
tc := tc
|
||||
state := testState(t)
|
||||
step := new(StepWaitStartupScript)
|
||||
c := state.Get("config").(*Config)
|
||||
d := state.Get("driver").(*DriverMock)
|
||||
|
||||
c.StartupScriptFile = "startup.sh"
|
||||
c.WrapStartupScriptFile = tc.WrapStartup
|
||||
c.Zone = "test-zone"
|
||||
state.Put("instance_name", "test-instance-name")
|
||||
|
||||
// This step stops when it gets Done back from the metadata.
|
||||
d.GetInstanceMetadataResult = tc.Result
|
||||
|
||||
// Run the step.
|
||||
assert.Equal(t, step.Run(context.Background(), state), multistep.ActionContinue, "Step should have continued.")
|
||||
|
||||
assert.Equal(t, d.GetInstanceMetadataResult, tc.Result, "MetadataResult was not the expected value.")
|
||||
assert.Equal(t, d.GetInstanceMetadataZone, tc.Zone, "Zone was not the expected value.")
|
||||
assert.Equal(t, d.GetInstanceMetadataName, tc.MetadataName, "Instance name was not the expected value.")
|
||||
}
|
||||
}
|
||||
|
|
|
@ -148,8 +148,21 @@
|
|||
- `source_image_project_id` ([]string) - A list of project IDs to search for the source image. Packer will search the first
|
||||
project ID in the list first, and fall back to the next in the list, until it finds the source image.
|
||||
|
||||
- `startup_script_file` (string) - The path to a startup script to run on the VM from which the image will
|
||||
be made.
|
||||
- `startup_script_file` (string) - The path to a startup script to run on the launched instance from which the image will
|
||||
be made. When set, the contents of the startup script file will be added to the instance metadata
|
||||
under the `"startup_script"` metadata property. See [Providing startup script contents directly](https://cloud.google.com/compute/docs/startupscript#providing_startup_script_contents_directly) for more details.
|
||||
|
||||
When using `startup_script_file` the following rules apply:
|
||||
- The contents of the script file will overwrite the value of the `"startup_script"` metadata property at runtime.
|
||||
- The contents of the script file will be wrapped in Packer's startup script wrapper, unless `wrap_startup_script` is disabled. See `wrap_startup_script` for more details.
|
||||
- Not supported by Windows instances. See [Startup Scripts for Windows](https://cloud.google.com/compute/docs/startupscript#providing_a_startup_script_for_windows_instances) for more details.
|
||||
|
||||
- `wrap_startup_script` (boolean) - For backwards compatibility this option defaults to `"true"` in the future it will default to `"false"`.
|
||||
If "true", the contents of `startup_script_file` or `"startup_script"` in the instance metadata
|
||||
is wrapped in a Packer specific script that tracks the execution and completion of the provided
|
||||
startup script. The wrapper ensures that the builder will not continue until the startup script has been executed.
|
||||
- The use of the wrapped script file requires that the user or service account
|
||||
running the build has the compute.instance.Metadata role.
|
||||
|
||||
- `subnetwork` (string) - The Google Compute subnetwork id or URL to use for the launched
|
||||
instance. Only required if the network has been created with custom
|
||||
|
|
Loading…
Reference in New Issue