diff --git a/.web-docs/components/builder/instance/README.md b/.web-docs/components/builder/instance/README.md index de08914..93ed2cb 100644 --- a/.web-docs/components/builder/instance/README.md +++ b/.web-docs/components/builder/instance/README.md @@ -90,6 +90,15 @@ The configuration arguments for the builder. Arguments can either be required or for testing provisioner logic without incurring the cost of image creation. Defaults to `false`. +- `user_data` (string) - User data for instance initialization systems such as cloud-init. The + value is a UTF-8 string and will be Base64-encoded by the plugin before + transmission. The maximum size is 32 KiB, measured before encoding. Use + Packer's built-in functions such as `file` to read content from disk. + Packer does not wait for user data to finish executing before shutting + down the instance. If your user data must complete before the image is + created, run `cloud-init status --wait` or an equivalent in a + provisioner. + diff --git a/component/builder/instance/config.go b/component/builder/instance/config.go index 8eed7b9..26304f9 100644 --- a/component/builder/instance/config.go +++ b/component/builder/instance/config.go @@ -103,6 +103,16 @@ type Config struct { // Defaults to `false`. SkipCreateImage bool `mapstructure:"skip_create_image" required:"false"` + // User data for instance initialization systems such as cloud-init. The + // value is a UTF-8 string and will be Base64-encoded by the plugin before + // transmission. The maximum size is 32 KiB, measured before encoding. Use + // Packer's built-in functions such as `file` to read content from disk. + // Packer does not wait for user data to finish executing before shutting + // down the instance. If your user data must complete before the image is + // created, run `cloud-init status --wait` or an equivalent in a + // provisioner. + UserData string `mapstructure:"user_data" required:"false"` + ctx interpolate.Context } @@ -199,6 +209,13 @@ func (c *Config) Prepare(args ...any) ([]string, error) { ) } + if len(c.UserData) > 32*1024 { + multiErr = packer.MultiErrorAppend( + multiErr, + errors.New("user_data must be 32 KiB or less of unencoded data"), + ) + } + if multiErr != nil && len(multiErr.Errors) > 0 { return nil, multiErr } diff --git a/component/builder/instance/config.hcl2spec.go b/component/builder/instance/config.hcl2spec.go index f651950..3c290fc 100644 --- a/component/builder/instance/config.hcl2spec.go +++ b/component/builder/instance/config.hcl2spec.go @@ -86,6 +86,7 @@ type FlatConfig struct { ArtifactOS *string `mapstructure:"artifact_os" cty:"artifact_os" hcl:"artifact_os"` ArtifactVersion *string `mapstructure:"artifact_version" cty:"artifact_version" hcl:"artifact_version"` SkipCreateImage *bool `mapstructure:"skip_create_image" required:"false" cty:"skip_create_image" hcl:"skip_create_image"` + UserData *string `mapstructure:"user_data" required:"false" cty:"user_data" hcl:"user_data"` } // FlatMapstructure returns a new FlatConfig. @@ -176,6 +177,7 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec { "artifact_os": &hcldec.AttrSpec{Name: "artifact_os", Type: cty.String, Required: false}, "artifact_version": &hcldec.AttrSpec{Name: "artifact_version", Type: cty.String, Required: false}, "skip_create_image": &hcldec.AttrSpec{Name: "skip_create_image", Type: cty.Bool, Required: false}, + "user_data": &hcldec.AttrSpec{Name: "user_data", Type: cty.String, Required: false}, } return s } diff --git a/component/builder/instance/step_instance_create.go b/component/builder/instance/step_instance_create.go index c6cae84..8e83dd3 100644 --- a/component/builder/instance/step_instance_create.go +++ b/component/builder/instance/step_instance_create.go @@ -6,6 +6,7 @@ package instance import ( "context" + "encoding/base64" "fmt" "time" @@ -111,6 +112,12 @@ func (o *stepInstanceCreate) Run( return res }(config.SSHPublicKeys), + UserData: func(userData string) string { + if userData == "" { + return "" + } + return base64.StdEncoding.EncodeToString([]byte(userData)) + }(config.UserData), }, }) if err != nil { diff --git a/docs-partials/component/builder/instance/Config-not-required.mdx b/docs-partials/component/builder/instance/Config-not-required.mdx index 76368a4..6b2b44f 100644 --- a/docs-partials/component/builder/instance/Config-not-required.mdx +++ b/docs-partials/component/builder/instance/Config-not-required.mdx @@ -51,4 +51,13 @@ for testing provisioner logic without incurring the cost of image creation. Defaults to `false`. +- `user_data` (string) - User data for instance initialization systems such as cloud-init. The + value is a UTF-8 string and will be Base64-encoded by the plugin before + transmission. The maximum size is 32 KiB, measured before encoding. Use + Packer's built-in functions such as `file` to read content from disk. + Packer does not wait for user data to finish executing before shutting + down the instance. If your user data must complete before the image is + created, run `cloud-init status --wait` or an equivalent in a + provisioner. +