From fab3f0b869562ad98833973c4df809fcc75d1426 Mon Sep 17 00:00:00 2001 From: Joseph Wright Date: Sat, 25 Apr 2026 18:13:35 -0400 Subject: [PATCH 1/4] Enable passing custom tags to AMIs --- cmd/easyto/tree/ami.go | 24 ++++++++++++++++++++++++ packer/fast/build.pkr.hcl | 9 +++++++-- packer/slow/build.pkr.hcl | 9 +++++++-- 3 files changed, 38 insertions(+), 4 deletions(-) diff --git a/cmd/easyto/tree/ami.go b/cmd/easyto/tree/ami.go index d2d3975..ec74510 100644 --- a/cmd/easyto/tree/ami.go +++ b/cmd/easyto/tree/ami.go @@ -97,9 +97,16 @@ var ( return fmt.Errorf("unexpected value for services: %w", err) } + quotedTags := bytes.NewBufferString("") + err = json.NewEncoder(quotedTags).Encode(parseTags(amiCfg.tags)) + if err != nil { + return fmt.Errorf("unexpected value for tags: %w", err) + } + packerArgs := []string{ "build", "-var", fmt.Sprintf("ami_name=%s", amiCfg.amiName), + "-var", fmt.Sprintf("ami_tags=%s", quotedTags.String()), "-var", fmt.Sprintf("builder_instance_type=%s", amiCfg.builderInstanceType), "-var", fmt.Sprintf("container_image=%s", amiCfg.containerImage), "-var", fmt.Sprintf("debug=%t", amiCfg.debug), @@ -164,6 +171,7 @@ type amiConfig struct { size int sshInterface string subnetID string + tags []string } func init() { @@ -240,6 +248,9 @@ func init() { AMICmd.Flags().BoolVar(&amiCfg.debug, "debug", false, "Enable debug output.") AMICmd.Flags().BoolVar(&amiCfg.public, "public", false, "Make the AMI and its snapshots public.") + + AMICmd.Flags().StringArrayVar(&amiCfg.tags, "tag", []string{}, + "Tag to apply to the AMI in the form key=value. May be specified multiple times. If no '=' is given, the tag has no value.") } func expandPath(pth string) (string, error) { @@ -279,6 +290,19 @@ func validateSSHInterface(sshInterface string) error { } } +func parseTags(rawTags []string) map[string]string { + tags := map[string]string{} + for _, t := range rawTags { + parts := strings.SplitN(t, "=", 2) + if len(parts) == 1 { + tags[parts[0]] = "" + continue + } + tags[parts[0]] = parts[1] + } + return tags +} + func validateBuilderImageMode(mode, builderImage string) error { if mode == "" { return nil diff --git a/packer/fast/build.pkr.hcl b/packer/fast/build.pkr.hcl index bd95e99..a2c78a5 100644 --- a/packer/fast/build.pkr.hcl +++ b/packer/fast/build.pkr.hcl @@ -11,6 +11,11 @@ variable "ami_name" { type = string } +variable "ami_tags" { + type = map(string) + default = {} +} + variable "architecture" { type = string default = "x86_64" @@ -104,9 +109,9 @@ source "amazon-ebssurrogate" "builder_ami" { ssh_username = var.ssh_username ssh_file_transfer_method = "sftp" subnet_id = var.subnet_id - tags = { + tags = merge(var.ami_tags, { "container_image" = var.container_image - } + }) ami_root_device { delete_on_termination = true diff --git a/packer/slow/build.pkr.hcl b/packer/slow/build.pkr.hcl index b16ec3b..9196eed 100644 --- a/packer/slow/build.pkr.hcl +++ b/packer/slow/build.pkr.hcl @@ -11,6 +11,11 @@ variable "ami_name" { type = string } +variable "ami_tags" { + type = map(string) + default = {} +} + variable "architecture" { type = string default = "x86_64" @@ -122,9 +127,9 @@ source "amazon-ebssurrogate" "builder_ami" { ssh_username = var.ssh_username ssh_file_transfer_method = "sftp" subnet_id = var.subnet_id - tags = { + tags = merge(var.ami_tags, { "container_image" = var.container_image - } + }) ami_root_device { delete_on_termination = true From 34ac5826be680ad8d419a2390da772d2cdc4a1b3 Mon Sep 17 00:00:00 2001 From: Joseph Wright Date: Sat, 25 Apr 2026 19:00:55 -0400 Subject: [PATCH 2/4] Rename default AMI tag key Update the default AMI tag key from `container_image` to `cloudboss.co/easyto/container-image`. --- packer/fast/build.pkr.hcl | 2 +- packer/slow/build.pkr.hcl | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packer/fast/build.pkr.hcl b/packer/fast/build.pkr.hcl index a2c78a5..26a5413 100644 --- a/packer/fast/build.pkr.hcl +++ b/packer/fast/build.pkr.hcl @@ -110,7 +110,7 @@ source "amazon-ebssurrogate" "builder_ami" { ssh_file_transfer_method = "sftp" subnet_id = var.subnet_id tags = merge(var.ami_tags, { - "container_image" = var.container_image + "cloudboss.co/easyto/container-image" = var.container_image }) ami_root_device { diff --git a/packer/slow/build.pkr.hcl b/packer/slow/build.pkr.hcl index 9196eed..4b92cfc 100644 --- a/packer/slow/build.pkr.hcl +++ b/packer/slow/build.pkr.hcl @@ -128,7 +128,7 @@ source "amazon-ebssurrogate" "builder_ami" { ssh_file_transfer_method = "sftp" subnet_id = var.subnet_id tags = merge(var.ami_tags, { - "container_image" = var.container_image + "cloudboss.co/easyto/container-image" = var.container_image }) ami_root_device { From 1ab784b0d0ba5e4efd33bf7e0c27c3191c2d7b71 Mon Sep 17 00:00:00 2001 From: Joseph Wright Date: Sat, 25 Apr 2026 19:35:55 -0400 Subject: [PATCH 3/4] Use afero filesystem in readlink This allows TestNewBuilder to pass on hosts where /dev/loop0 does not exist by backing the device path with an in-memory filesystem, while TestReadlink continues to exercise real symlink resolution via OsFs. --- pkg/ctr2disk/ctr2disk.go | 16 ++++++++++++---- pkg/ctr2disk/ctr2disk_test.go | 6 +++++- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/pkg/ctr2disk/ctr2disk.go b/pkg/ctr2disk/ctr2disk.go index 2a09385..0d71a73 100644 --- a/pkg/ctr2disk/ctr2disk.go +++ b/pkg/ctr2disk/ctr2disk.go @@ -186,7 +186,7 @@ func NewBuilder(fs afero.Fs, opts ...BuilderOpt) (*Builder, error) { builder.pathInit = filepath.Join(builder.AssetDir, archiveInit) builder.pathSSH = filepath.Join(builder.AssetDir, archiveSSH) - vmImageDevice, err := readlink(builder.VMImageDevice) + vmImageDevice, err := readlink(fs, builder.VMImageDevice) if err != nil { return nil, fmt.Errorf("unable to resolve path of VM image device: %w", err) } @@ -741,13 +741,17 @@ func partitionName(disk string, partition int) string { return fmt.Sprintf("%s%d", disk, partition) } -func readlink(path string) (string, error) { +func readlink(fs afero.Fs, path string) (string, error) { abs, err := filepath.Abs(path) if err != nil { return "", err } - fi, err := os.Lstat(abs) + lst, ok := fs.(afero.Lstater) + if !ok { + return "", errors.New("filesystem does not support Lstat") + } + fi, _, err := lst.LstatIfPossible(abs) if err != nil { return "", err } @@ -756,7 +760,11 @@ func readlink(path string) (string, error) { return abs, nil } - target, err := os.Readlink(abs) + rdr, ok := fs.(afero.LinkReader) + if !ok { + return "", afero.ErrNoReadlink + } + target, err := rdr.ReadlinkIfPossible(abs) if err != nil { return "", err } diff --git a/pkg/ctr2disk/ctr2disk_test.go b/pkg/ctr2disk/ctr2disk_test.go index d5959e8..372dfcd 100644 --- a/pkg/ctr2disk/ctr2disk_test.go +++ b/pkg/ctr2disk/ctr2disk_test.go @@ -331,7 +331,7 @@ func TestReadlink(t *testing.T) { for _, tc := range testCases { t.Run(tc.description, func(t *testing.T) { - result, err := readlink(tc.path) + result, err := readlink(afero.NewOsFs(), tc.path) if tc.expectError { assert.Error(t, err) } else { @@ -452,6 +452,10 @@ func TestNewBuilder(t *testing.T) { err = testutil.WriteTarFile(testFS, kernelTar, kernelFiles) require.NoError(t, err) + require.NoError(t, testFS.MkdirAll("/dev", 0755)) + _, err = testFS.Create("/dev/loop0") + require.NoError(t, err) + testCases := []struct { description string opts []BuilderOpt From bc3e56355743398a2037e408ce508928ea268030 Mon Sep 17 00:00:00 2001 From: Joseph Wright Date: Sun, 26 Apr 2026 15:42:45 -0400 Subject: [PATCH 4/4] Update changelog for v0.10.0 --- CHANGELOG.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 24f92bd..0135274 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,19 @@ # Changelog +## [0.10.0] - 2026-04-26 + +### Added + +- Enable passing custom tags to AMIs. + +### Changed + +- Rename default AMI tag key for the container image from `container_image` to `cloudboss.co/easyto/container-image`. + +### Fixed + +- Fix readlink wrapper to take an `aferofs.Fs` argument to fix broken `ctr2disk.TestNewBuilder` test. This aligns it with most functions in `ctr2disk` that access the filesystem. + ## [0.9.0] - 2026-04-23 ### Added @@ -119,6 +133,7 @@ Initial release +[0.10.0]: https://github.com/cloudboss/easyto/releases/tag/v0.10.0 [0.9.0]: https://github.com/cloudboss/easyto/releases/tag/v0.9.0 [0.8.0]: https://github.com/cloudboss/easyto/releases/tag/v0.8.0 [0.7.0]: https://github.com/cloudboss/easyto/releases/tag/v0.7.0