Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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
Expand Down
24 changes: 24 additions & 0 deletions cmd/easyto/tree/ami.go
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand Down Expand Up @@ -164,6 +171,7 @@ type amiConfig struct {
size int
sshInterface string
subnetID string
tags []string
}

func init() {
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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
Expand Down
11 changes: 8 additions & 3 deletions packer/fast/build.pkr.hcl
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,11 @@ variable "ami_name" {
type = string
}

variable "ami_tags" {
type = map(string)
default = {}
}

variable "architecture" {
type = string
default = "x86_64"
Expand Down Expand Up @@ -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 = {
"container_image" = var.container_image
}
tags = merge(var.ami_tags, {
"cloudboss.co/easyto/container-image" = var.container_image
})

ami_root_device {
delete_on_termination = true
Expand Down
11 changes: 8 additions & 3 deletions packer/slow/build.pkr.hcl
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,11 @@ variable "ami_name" {
type = string
}

variable "ami_tags" {
type = map(string)
default = {}
}

variable "architecture" {
type = string
default = "x86_64"
Expand Down Expand Up @@ -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 = {
"container_image" = var.container_image
}
tags = merge(var.ami_tags, {
"cloudboss.co/easyto/container-image" = var.container_image
})

ami_root_device {
delete_on_termination = true
Expand Down
16 changes: 12 additions & 4 deletions pkg/ctr2disk/ctr2disk.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand Down Expand Up @@ -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
}
Expand All @@ -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
}
Expand Down
6 changes: 5 additions & 1 deletion pkg/ctr2disk/ctr2disk_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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
Expand Down
Loading