diff --git a/.web-docs/components/builder/instance/README.md b/.web-docs/components/builder/instance/README.md index f0b9084..5127ad4 100644 --- a/.web-docs/components/builder/instance/README.md +++ b/.web-docs/components/builder/instance/README.md @@ -32,6 +32,9 @@ The configuration arguments for the builder. Arguments can either be required or - `project` (string) - Name or ID of the project where the temporary instance and resulting image will be created. +- `artifact_name` (string) - Name of the resulting image artifact. Required unless `skip_create_image` + is `true`. + @@ -62,14 +65,12 @@ The configuration arguments for the builder. Arguments can either be required or - `subnet` (string) - Subnet to create the instance within. Defaults to `default`. -- `name` (string) - Name of the temporary instance. Defaults to `packer-BUILD_NAME-RUN_ID` where - `BUILD_NAME` is the Packer source name and `RUN_ID` is a short prefix of the - unique ID Packer assigns to the current run. This must be unique to prevent - Oxide instance name conflicts. +- `name` (string) - Name of the temporary instance and its boot disk, primary network interface, + and snapshot. Defaults to `packer-UUID`, where `UUID` is a unique identifier + generated for the current build. This must be unique to prevent Oxide + resource name conflicts. -- `hostname` (string) - Hostname of the temporary instance. Defaults to `packer-BUILD_NAME-RUN_ID` - where `BUILD_NAME` is the Packer source name and `RUN_ID` is a short prefix - of the unique ID Packer assigns to the current run. +- `hostname` (string) - Hostname of the temporary instance. Defaults to the value of `name`. - `cpus` (uint64) - Number of vCPUs to provision the instance with. Defaults to `1`. @@ -78,14 +79,8 @@ The configuration arguments for the builder. Arguments can either be required or - `ssh_public_keys` ([]string) - An array of names or IDs of SSH public keys to inject into the instance. -- `artifact_name` (string) - Name of the resulting image artifact. Defaults to - `SOURCE_IMAGE_NAME-BUILD_NAME-RUN_ID` where `SOURCE_IMAGE_NAME` is the name - of the source image as retrieved from Oxide, `BUILD_NAME` is the Packer - source name, and `RUN_ID` is a short prefix of the unique ID Packer assigns - to the current run. - -- `artifact_description` (string) - Description of the resulting image artifact. Defaults to the description of - the source image as retrieved from Oxide. +- `artifact_description` (string) - Description of the resulting image artifact. Defaults to a description that + identifies the Packer build that created it. - `artifact_os` (string) - Operating system of the resulting image artifact. Defaults to the OS of the source image as retrieved from Oxide. @@ -111,6 +106,13 @@ The configuration arguments for the builder. Arguments can either be required or +## Temporary Resources + +Generated temporary resources use the name `packer-UUID`, where `UUID` is the +unique identifier generated for the current build. The builder logs the names +and IDs of created instances, snapshots, and SSH keys and sets temporary +resource descriptions to identify the build that created them. + ## Interpolation This builder does not support Go template interpolation (e.g., `{{timestamp}}`). @@ -145,8 +147,9 @@ Packer to connect to the instance. The name of the temporary SSH public key uploaded to Oxide can be set using the [`temporary_key_pair_name`](/packer/docs/communicators/ssh#temporary_key_pair_name) -argument. Generally there's no reason to set this but it's available should it -be necessary. +argument. Generally there's no reason to set this but it's available should +it be necessary. It defaults to `packer-UUID`, where `UUID` is the unique +identifier generated for the current build. ## Provisioner @@ -206,6 +209,7 @@ with the builder. source "oxide-instance" "example" { project = "packer-acc-test" boot_disk_image_id = "feb2c8ee-5a1d-4d66-beeb-289b860561bf" + artifact_name = "packer-${formatdate("YYYYMMDD-hhmmss", timestamp())}" # SSH communicator configuration. ssh_username = "ubuntu" diff --git a/component/builder/instance/config.go b/component/builder/instance/config.go index cdb25d6..fc5e5a6 100644 --- a/component/builder/instance/config.go +++ b/component/builder/instance/config.go @@ -10,8 +10,6 @@ package instance import ( "errors" "fmt" - "os" - "strings" "github.com/hashicorp/packer-plugin-sdk/common" "github.com/hashicorp/packer-plugin-sdk/communicator" @@ -68,15 +66,13 @@ type Config struct { // Subnet to create the instance within. Defaults to `default`. Subnet string `mapstructure:"subnet"` - // Name of the temporary instance. Defaults to `packer-BUILD_NAME-RUN_ID` where - // `BUILD_NAME` is the Packer source name and `RUN_ID` is a short prefix of the - // unique ID Packer assigns to the current run. This must be unique to prevent - // Oxide instance name conflicts. + // Name of the temporary instance and its boot disk, primary network interface, + // and snapshot. Defaults to `packer-UUID`, where `UUID` is a unique identifier + // generated for the current build. This must be unique to prevent Oxide + // resource name conflicts. Name string `mapstructure:"name"` - // Hostname of the temporary instance. Defaults to `packer-BUILD_NAME-RUN_ID` - // where `BUILD_NAME` is the Packer source name and `RUN_ID` is a short prefix - // of the unique ID Packer assigns to the current run. + // Hostname of the temporary instance. Defaults to the value of `name`. Hostname string `mapstructure:"hostname"` // Number of vCPUs to provision the instance with. Defaults to `1`. @@ -89,15 +85,12 @@ type Config struct { // An array of names or IDs of SSH public keys to inject into the instance. SSHPublicKeys []string `mapstructure:"ssh_public_keys"` - // Name of the resulting image artifact. Defaults to - // `SOURCE_IMAGE_NAME-BUILD_NAME-RUN_ID` where `SOURCE_IMAGE_NAME` is the name - // of the source image as retrieved from Oxide, `BUILD_NAME` is the Packer - // source name, and `RUN_ID` is a short prefix of the unique ID Packer assigns - // to the current run. - ArtifactName string `mapstructure:"artifact_name"` + // Name of the resulting image artifact. Required unless `skip_create_image` + // is `true`. + ArtifactName string `mapstructure:"artifact_name" required:"true"` - // Description of the resulting image artifact. Defaults to the description of - // the source image as retrieved from Oxide. + // Description of the resulting image artifact. Defaults to a description that + // identifies the Packer build that created it. ArtifactDescription string `mapstructure:"artifact_description"` // Operating system of the resulting image artifact. Defaults to the OS of the @@ -124,6 +117,9 @@ type Config struct { // created, run `cloud-init status --wait` or an equivalent in a // provisioner. UserData string `mapstructure:"user_data" required:"false"` + + // Unique suffix shared by generated resource names for this build. + generatedSuffix string } // Prepare decodes the configuration and validates it. @@ -141,11 +137,15 @@ func (c *Config) Prepare(args ...any) ([]string, error) { // Set defaults. { if c.Name == "" { - c.Name = fmt.Sprintf("packer-%s", c.uniqueSuffix()) + c.Name = c.uniqueName("packer") } if c.Hostname == "" { - c.Hostname = fmt.Sprintf("packer-%s", c.uniqueSuffix()) + c.Hostname = c.Name + } + + if c.ArtifactDescription == "" { + c.ArtifactDescription = c.buildDescription() } if c.CPUs == 0 { @@ -178,7 +178,7 @@ func (c *Config) Prepare(args ...any) ([]string, error) { } if c.Comm.SSHTemporaryKeyPairName == "" { - c.Comm.SSHTemporaryKeyPairName = fmt.Sprintf("packer-%s", c.uniqueSuffix()) + c.Comm.SSHTemporaryKeyPairName = c.uniqueName("packer") } c.Comm.SSHTemporaryKeyPairType = "ed25519" @@ -194,6 +194,13 @@ func (c *Config) Prepare(args ...any) ([]string, error) { ) } + if c.ArtifactName == "" && !c.SkipCreateImage { + multiErr = packer.MultiErrorAppend( + multiErr, + errors.New("artifact_name is required unless skip_create_image is true"), + ) + } + if len(c.UserData) > 32*1024 { multiErr = packer.MultiErrorAppend( multiErr, @@ -211,43 +218,27 @@ func (c *Config) Prepare(args ...any) ([]string, error) { return nil, nil } -// uniqueSuffix returns an identifier, derived from Packer-provided values, -// that is used to configure resource names that are unique and traceable to the -// Packer build that created them. -// -// The following Packer-provided values are used to generate the identifier. -// -// - [common.PackerConfig.PackerBuildName]: The build source name which is -// unique for each build in a Packer configuration. -// - PACKER_RUN_UUID: The unique ID Packer assigned to the current run, which is -// shared among the builds in a Packer configuration. When this is unset, it -// falls back to a generated UUID that's unique for each build. This value is -// truncated to 8 characters to keep the generated identifier within Oxide's -// 63-character name limit. -func (c *Config) uniqueSuffix() string { - runID := os.Getenv("PACKER_RUN_UUID") - if runID == "" { - runID = uuid.TimeOrderedUUID() +// buildDescription returns a description identifying the Packer build that +// created a resource. +func (c *Config) buildDescription() string { + if c.PackerBuildName == "" { + return "Created by Packer." } - if len(runID) > 8 { - runID = runID[:8] - } + return fmt.Sprintf("Created by Packer build %q.", c.PackerBuildName) +} - // Transform the Packer build name into a name that the Oxide API will accept. - // This is mainly here to transform `_` into `-`. - buildName := strings.Map(func(r rune) rune { - switch { - case r >= 'a' && r <= 'z', - r >= 'A' && r <= 'Z', - r >= '0' && r <= '9', - r == '-': - return r - default: - return '-' - } - }, c.PackerBuildName) - buildName = strings.Trim(buildName, "-") +// uniqueName returns a name containing the unique suffix for this build. +func (c *Config) uniqueName(prefix string) string { + return fmt.Sprintf("%s-%s", prefix, c.uniqueSuffix()) +} + +// uniqueSuffix returns the identifier used to generate unique resource names +// for this build. +func (c *Config) uniqueSuffix() string { + if c.generatedSuffix == "" { + c.generatedSuffix = uuid.TimeOrderedUUID() + } - return fmt.Sprintf("%s-%s", buildName, runID) + return c.generatedSuffix } diff --git a/component/builder/instance/config.hcl2spec.go b/component/builder/instance/config.hcl2spec.go index bc92f1b..45ab26e 100644 --- a/component/builder/instance/config.hcl2spec.go +++ b/component/builder/instance/config.hcl2spec.go @@ -82,7 +82,7 @@ type FlatConfig struct { CPUs *uint64 `mapstructure:"cpus" cty:"cpus" hcl:"cpus"` Memory *uint64 `mapstructure:"memory" cty:"memory" hcl:"memory"` SSHPublicKeys []string `mapstructure:"ssh_public_keys" cty:"ssh_public_keys" hcl:"ssh_public_keys"` - ArtifactName *string `mapstructure:"artifact_name" cty:"artifact_name" hcl:"artifact_name"` + ArtifactName *string `mapstructure:"artifact_name" required:"true" cty:"artifact_name" hcl:"artifact_name"` ArtifactDescription *string `mapstructure:"artifact_description" cty:"artifact_description" hcl:"artifact_description"` ArtifactOS *string `mapstructure:"artifact_os" cty:"artifact_os" hcl:"artifact_os"` ArtifactVersion *string `mapstructure:"artifact_version" cty:"artifact_version" hcl:"artifact_version"` diff --git a/component/builder/instance/config_test.go b/component/builder/instance/config_test.go new file mode 100644 index 0000000..6d73a53 --- /dev/null +++ b/component/builder/instance/config_test.go @@ -0,0 +1,122 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +package instance + +import ( + "regexp" + "strings" + "testing" +) + +func TestUniqueSuffix(t *testing.T) { + t.Setenv("PACKER_RUN_UUID", "packer-run-uuid") + + config := &Config{} + suffix := config.uniqueSuffix() + + if suffix != config.uniqueSuffix() { + t.Fatal("uniqueSuffix returned different values for the same config") + } + + uuidPattern := regexp.MustCompile( + `^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$`, + ) + if !uuidPattern.MatchString(suffix) { + t.Fatalf("uniqueSuffix returned invalid UUID %q", suffix) + } +} + +func TestUniqueName(t *testing.T) { + config := &Config{} + prefix := "packer" + name := config.uniqueName(prefix) + + if got, want := name, prefix+"-"+config.uniqueSuffix(); got != want { + t.Fatalf("uniqueName returned %q; want %q", got, want) + } +} + +func TestBuildDescription(t *testing.T) { + tests := []struct { + name string + buildName string + want string + }{ + { + name: "build name", + buildName: "service-image", + want: `Created by Packer build "service-image".`, + }, + { + name: "missing build name", + want: "Created by Packer.", + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + config := &Config{} + config.PackerBuildName = test.buildName + + if got := config.buildDescription(); got != test.want { + t.Fatalf("buildDescription returned %q; want %q", got, test.want) + } + }) + } +} + +func TestPrepareGeneratedNames(t *testing.T) { + config := &Config{} + _, err := config.Prepare(map[string]any{ + "artifact_name": "artifact", + "boot_disk_image_id": "image-id", + "communicator": "none", + "packer_build_name": "service-image", + "project": "project", + }) + if err != nil { + t.Fatalf("Prepare returned an error: %v", err) + } + + if config.Hostname != config.Name { + t.Fatalf("Hostname is %q; want %q", config.Hostname, config.Name) + } + if got, want := config.ArtifactDescription, + `Created by Packer build "service-image".`; got != want { + t.Fatalf("ArtifactDescription is %q; want %q", got, want) + } + if config.Comm.SSHTemporaryKeyPairName != config.Name { + t.Fatalf( + "SSHTemporaryKeyPairName is %q; want %q", + config.Comm.SSHTemporaryKeyPairName, + config.Name, + ) + } +} + +func TestPrepareRequiresArtifactName(t *testing.T) { + config := &Config{} + _, err := config.Prepare(map[string]any{ + "boot_disk_image_id": "image-id", + "communicator": "none", + "project": "project", + }) + if err == nil || !strings.Contains(err.Error(), "artifact_name is required") { + t.Fatalf("Prepare returned error %v", err) + } +} + +func TestPrepareAllowsMissingArtifactNameWhenSkippingImage(t *testing.T) { + config := &Config{} + _, err := config.Prepare(map[string]any{ + "boot_disk_image_id": "image-id", + "communicator": "none", + "project": "project", + "skip_create_image": true, + }) + if err != nil { + t.Fatalf("Prepare returned an error: %v", err) + } +} diff --git a/component/builder/instance/step_image_create.go b/component/builder/instance/step_image_create.go index 172de36..2a08953 100644 --- a/component/builder/instance/step_image_create.go +++ b/component/builder/instance/step_image_create.go @@ -78,7 +78,7 @@ func (s *stepImageCreate) Run( return multistep.ActionHalt } - ui.Sayf("Created Oxide image: %s", image.Id) + ui.Sayf("Created Oxide image %s (%s)", image.Name, image.Id) stateBag.Put("image_id", string(image.Id)) stateBag.Put("image_name", string(image.Name)) diff --git a/component/builder/instance/step_image_view.go b/component/builder/instance/step_image_view.go index 47b7938..67061d1 100644 --- a/component/builder/instance/step_image_view.go +++ b/component/builder/instance/step_image_view.go @@ -6,7 +6,6 @@ package instance import ( "context" - "fmt" "github.com/hashicorp/packer-plugin-sdk/multistep" "github.com/hashicorp/packer-plugin-sdk/packer" @@ -39,14 +38,6 @@ func (s *stepImageView) Run(ctx context.Context, stateBag multistep.StateBag) mu stateBag.Put("source_image_id", string(image.Id)) - if config.ArtifactName == "" { - config.ArtifactName = fmt.Sprintf("%s-%s", image.Name, config.uniqueSuffix()) - } - - if config.ArtifactDescription == "" { - config.ArtifactDescription = image.Description - } - if config.ArtifactOS == "" { config.ArtifactOS = image.Os } diff --git a/component/builder/instance/step_instance_create.go b/component/builder/instance/step_instance_create.go index 8577356..1a4c50f 100644 --- a/component/builder/instance/step_instance_create.go +++ b/component/builder/instance/step_instance_create.go @@ -31,6 +31,7 @@ func (o *stepInstanceCreate) Run( config := stateBag.Get("config").(*Config) ui.Say("Creating Oxide instance") + description := config.buildDescription() instance, err := oxideClient.InstanceCreate(ctx, oxide.InstanceCreateParams{ Project: oxide.NameOrId(config.Project), @@ -39,7 +40,7 @@ func (o *stepInstanceCreate) Run( BootDisk: oxide.InstanceDiskAttachment{ Value: &oxide.InstanceDiskAttachmentCreate{ Name: oxide.Name(config.Name), - Description: "Created by Packer.", + Description: description, Size: oxide.ByteCount(config.BootDiskSize), DiskBackend: oxide.DiskBackend{ Value: &oxide.DiskBackendDistributed{ @@ -52,7 +53,7 @@ func (o *stepInstanceCreate) Run( }, }, }, - Description: "Created by Packer.", + Description: description, ExternalIps: []oxide.ExternalIpCreate{ { Value: &oxide.ExternalIpCreateEphemeral{ @@ -83,7 +84,7 @@ func (o *stepInstanceCreate) Run( Params: []oxide.InstanceNetworkInterfaceCreate{ { Name: oxide.Name(config.Name), - Description: "Created by Packer.", + Description: description, SubnetName: oxide.Name(config.Subnet), VpcName: oxide.Name(config.VPC), IpConfig: oxide.PrivateIpStackCreate{ @@ -127,7 +128,7 @@ func (o *stepInstanceCreate) Run( return multistep.ActionHalt } - ui.Sayf("Created Oxide instance: %s", instance.Id) + ui.Sayf("Created Oxide instance %s (%s)", instance.Name, instance.Id) stateBag.Put("instance_id", instance.Id) stateBag.Put("boot_disk_id", instance.BootDiskId) diff --git a/component/builder/instance/step_snapshot_create.go b/component/builder/instance/step_snapshot_create.go index 65c8c09..3fbcb50 100644 --- a/component/builder/instance/step_snapshot_create.go +++ b/component/builder/instance/step_snapshot_create.go @@ -43,7 +43,7 @@ func (s *stepSnapshotCreate) Run( Project: oxide.NameOrId(config.Project), Body: &oxide.SnapshotCreate{ Name: oxide.Name(config.Name), - Description: "Created by Packer.", + Description: config.buildDescription(), Disk: oxide.NameOrId(bootDiskID), }, }) @@ -53,7 +53,7 @@ func (s *stepSnapshotCreate) Run( return multistep.ActionHalt } - ui.Sayf("Created Oxide snapshot: %s", snapshot.Id) + ui.Sayf("Created Oxide snapshot %s (%s)", snapshot.Name, snapshot.Id) stateBag.Put("snapshot_id", snapshot.Id) diff --git a/component/builder/instance/step_ssh_key_create.go b/component/builder/instance/step_ssh_key_create.go index 347f8e2..74aab6a 100644 --- a/component/builder/instance/step_ssh_key_create.go +++ b/component/builder/instance/step_ssh_key_create.go @@ -36,7 +36,7 @@ func (s *stepSSHKeyCreate) Run( sshKey, err := oxideClient.CurrentUserSshKeyCreate(ctx, oxide.CurrentUserSshKeyCreateParams{ Body: &oxide.SshKeyCreate{ - Description: "Created by Packer.", + Description: config.buildDescription(), Name: oxide.Name(config.Comm.SSHTemporaryKeyPairName), PublicKey: string(config.Comm.SSHPublicKey), }, @@ -47,7 +47,7 @@ func (s *stepSSHKeyCreate) Run( return multistep.ActionHalt } - ui.Sayf("Created Oxide SSH public key: %s", sshKey.Id) + ui.Sayf("Created Oxide SSH public key %s (%s)", sshKey.Name, sshKey.Id) stateBag.Put("ssh_public_key_name", sshKey.Name) stateBag.Put("ssh_public_key_id", sshKey.Id) diff --git a/docs-partials/component/builder/instance/Config-not-required.mdx b/docs-partials/component/builder/instance/Config-not-required.mdx index e1dde3e..51aac4f 100644 --- a/docs-partials/component/builder/instance/Config-not-required.mdx +++ b/docs-partials/component/builder/instance/Config-not-required.mdx @@ -23,14 +23,12 @@ - `subnet` (string) - Subnet to create the instance within. Defaults to `default`. -- `name` (string) - Name of the temporary instance. Defaults to `packer-BUILD_NAME-RUN_ID` where - `BUILD_NAME` is the Packer source name and `RUN_ID` is a short prefix of the - unique ID Packer assigns to the current run. This must be unique to prevent - Oxide instance name conflicts. +- `name` (string) - Name of the temporary instance and its boot disk, primary network interface, + and snapshot. Defaults to `packer-UUID`, where `UUID` is a unique identifier + generated for the current build. This must be unique to prevent Oxide + resource name conflicts. -- `hostname` (string) - Hostname of the temporary instance. Defaults to `packer-BUILD_NAME-RUN_ID` - where `BUILD_NAME` is the Packer source name and `RUN_ID` is a short prefix - of the unique ID Packer assigns to the current run. +- `hostname` (string) - Hostname of the temporary instance. Defaults to the value of `name`. - `cpus` (uint64) - Number of vCPUs to provision the instance with. Defaults to `1`. @@ -39,14 +37,8 @@ - `ssh_public_keys` ([]string) - An array of names or IDs of SSH public keys to inject into the instance. -- `artifact_name` (string) - Name of the resulting image artifact. Defaults to - `SOURCE_IMAGE_NAME-BUILD_NAME-RUN_ID` where `SOURCE_IMAGE_NAME` is the name - of the source image as retrieved from Oxide, `BUILD_NAME` is the Packer - source name, and `RUN_ID` is a short prefix of the unique ID Packer assigns - to the current run. - -- `artifact_description` (string) - Description of the resulting image artifact. Defaults to the description of - the source image as retrieved from Oxide. +- `artifact_description` (string) - Description of the resulting image artifact. Defaults to a description that + identifies the Packer build that created it. - `artifact_os` (string) - Operating system of the resulting image artifact. Defaults to the OS of the source image as retrieved from Oxide. diff --git a/docs-partials/component/builder/instance/Config-required.mdx b/docs-partials/component/builder/instance/Config-required.mdx index bc91f7e..0077121 100644 --- a/docs-partials/component/builder/instance/Config-required.mdx +++ b/docs-partials/component/builder/instance/Config-required.mdx @@ -6,4 +6,7 @@ - `project` (string) - Name or ID of the project where the temporary instance and resulting image will be created. +- `artifact_name` (string) - Name of the resulting image artifact. Required unless `skip_create_image` + is `true`. + diff --git a/docs/builders/instance.mdx b/docs/builders/instance.mdx index bb91c89..b1de162 100644 --- a/docs/builders/instance.mdx +++ b/docs/builders/instance.mdx @@ -27,6 +27,13 @@ Type: `oxide-instance` @include 'component/builder/instance/Config-not-required.mdx' +## Temporary Resources + +Generated temporary resources use the name `packer-UUID`, where `UUID` is the +unique identifier generated for the current build. The builder logs the names +and IDs of created instances, snapshots, and SSH keys and sets temporary +resource descriptions to identify the build that created them. + ## Interpolation This builder does not support Go template interpolation (e.g., `{{timestamp}}`). @@ -61,8 +68,9 @@ Packer to connect to the instance. The name of the temporary SSH public key uploaded to Oxide can be set using the [`temporary_key_pair_name`](/packer/docs/communicators/ssh#temporary_key_pair_name) -argument. Generally there's no reason to set this but it's available should it -be necessary. +argument. Generally there's no reason to set this but it's available should +it be necessary. It defaults to `packer-UUID`, where `UUID` is the unique +identifier generated for the current build. ## Provisioner @@ -122,6 +130,7 @@ with the builder. source "oxide-instance" "example" { project = "packer-acc-test" boot_disk_image_id = "feb2c8ee-5a1d-4d66-beeb-289b860561bf" + artifact_name = "packer-${formatdate("YYYYMMDD-hhmmss", timestamp())}" # SSH communicator configuration. ssh_username = "ubuntu" diff --git a/example/template.pkr.hcl b/example/template.pkr.hcl index bfb1140..b3f2edf 100644 --- a/example/template.pkr.hcl +++ b/example/template.pkr.hcl @@ -14,6 +14,7 @@ data "oxide-image" "ubuntu" { source "oxide-instance" "example" { project = "packer-acc-test" boot_disk_image_id = data.oxide-image.ubuntu.image_id + artifact_name = "noble-${formatdate("YYYYMMDD-hhmmss", timestamp())}" # Do not connect to the temporary instance. communicator = "none"