Skip to content
Open
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
11 changes: 11 additions & 0 deletions docs/user/reference/config/images.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,12 @@ The `[images]` section defines system images (VMs, containers, etc.) that azldev
| Capabilities | `capabilities` | [ImageCapabilities](#image-capabilities) | No | Describes features and properties of this image |
| Tests | `tests` | [ImageTests](#image-tests) | No | Test configuration for this image |
| Publish | `publish` | [ImagePublish](#image-publish) | No | Publishing settings for this image |
| Architectures | `architectures` | string array | **Yes** | Architectures supported by this image |

The current supported architectures are `x86_64` and `aarch64`. Every image must
declare at least one architecture — omitting `architectures` is invalid. `azldev image list`
reports each image's architecture set, and `azldev image build --arch` rejects architectures
outside it.

## Image Definition

Expand Down Expand Up @@ -64,6 +70,7 @@ The `publish` subtable configures where an image is published. Unlike packages (
[images.vm-base]
description = "VM Base Image"
definition = { type = "kiwi", path = "vm-base/vm-base.kiwi" }
architectures = ["x86_64", "aarch64"]

[images.vm-base.capabilities]
machine-bootable = true
Expand All @@ -77,6 +84,7 @@ runtime-package-management = true
[images.container-base]
description = "Container Base Image"
definition = { type = "kiwi", path = "container-base/container-base.kiwi" }
architectures = ["x86_64", "aarch64"]

[images.container-base.capabilities]
container = true
Expand All @@ -88,6 +96,7 @@ container = true
[images.vm-azure]
description = "Azure-optimized VM image"
definition = { type = "kiwi", path = "vm-azure/vm-azure.kiwi", profile = "azure" }
architectures = ["x86_64"]
```

### Image with test suite references
Expand All @@ -96,6 +105,7 @@ definition = { type = "kiwi", path = "vm-azure/vm-azure.kiwi", profile = "azure"
[images.vm-base]
description = "VM Base Image"
definition = { type = "kiwi", path = "vm-base/vm-base.kiwi" }
architectures = ["x86_64", "aarch64"]

[images.vm-base.capabilities]
machine-bootable = true
Expand All @@ -114,6 +124,7 @@ test-suites = [
[images.vm-base]
description = "VM Base Image"
definition = { type = "kiwi", path = "vm-base/vm-base.kiwi" }
architectures = ["x86_64", "aarch64"]

[images.vm-base.publish]
channels = ["registry-prod", "registry-staging"]
Expand Down
4 changes: 4 additions & 0 deletions internal/app/azldev/agentskill/content/image.md.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ Images are declared under `[images.<name>]` (conventionally in an `images.toml`)
[images.container-base]
description = "Container base image"
definition = { type = "kiwi", path = "container-base/container-base.kiwi", profile = "core" }
architectures = ["x86_64", "aarch64"]

[images.container-base.capabilities]
container = true
Expand All @@ -50,6 +51,9 @@ definition = { type = "kiwi", path = "container-base/container-base.kiwi", profi
`profile` selects a kiwi profile (optional).
- `capabilities` are tri-state flags describing the image — `machine-bootable`,
`container`, `systemd`, `runtime-package-management`. Set only the ones that apply.
- `architectures = ["x86_64", "aarch64"]` or one of the two is **required** on every
image — there is no project-wide default to fall back to. It lists the architectures
this image supports; `image build --arch` enforces it.
- `tests.test-suites` lists the test suites `azldev image test` runs.
- `publish.channels` lists the channels the image publishes to.

Expand Down
48 changes: 46 additions & 2 deletions internal/app/azldev/cmds/image/build.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,15 @@ import (
"fmt"
"log/slog"
"path/filepath"
"runtime"
"slices"

"github.com/microsoft/azure-linux-dev-tools/internal/app/azldev"
"github.com/microsoft/azure-linux-dev-tools/internal/app/azldev/core/workdir"
"github.com/microsoft/azure-linux-dev-tools/internal/projectconfig"
"github.com/microsoft/azure-linux-dev-tools/internal/utils/fileutils"
"github.com/microsoft/azure-linux-dev-tools/internal/utils/kiwi"
"github.com/microsoft/azure-linux-dev-tools/internal/utils/qemu"
"github.com/spf13/cobra"
)

Expand Down Expand Up @@ -150,8 +153,7 @@ func BuildImage(env *azldev.Env, options *ImageBuildOptions) (*ImageBuildResult,
return nil, err
}

// Resolve the image from config.
imageConfig, err := ResolveImageByName(env, options.ImageName)
imageConfig, err := resolveBuildImage(env, options)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -232,6 +234,48 @@ func BuildImage(env *azldev.Env, options *ImageBuildOptions) (*ImageBuildResult,
}, nil
}

func resolveBuildImage(env *azldev.Env, options *ImageBuildOptions) (*projectconfig.ImageConfig, error) {
imageConfig, err := ResolveImageByName(env, options.ImageName)
if err != nil {
return nil, err
}

if err := validateBuildArchitecture(
imageConfig,
options.TargetArch,
runtime.GOARCH,
); err != nil {
return nil, err
}

return imageConfig, nil
}

func validateBuildArchitecture(
imageConfig *projectconfig.ImageConfig,
targetArch ImageArch,
hostGoArch string,
) error {
arch := string(targetArch)
if arch == "" {
arch = qemu.GoArchToQEMUArch(hostGoArch)
if !slices.Contains(qemu.SupportedArchitectures(), arch) {
return fmt.Errorf("unsupported host architecture %#q", hostGoArch)
}
}

if !imageConfig.SupportsArchitecture(arch) {
return fmt.Errorf(
"image %#q does not support architecture %#q; supported architectures: %q",
imageConfig.Name,
arch,
imageConfig.Architectures,
)
Comment thread
binujp marked this conversation as resolved.
}

return nil
}

// checkBuildPrerequisites verifies that required tools are available for building images.
func checkBuildPrerequisites(env *azldev.Env) error {
if err := kiwi.CheckPrerequisites(env); err != nil {
Expand Down
27 changes: 27 additions & 0 deletions internal/app/azldev/cmds/image/build_internal_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -113,3 +113,30 @@ func TestCreateKiwiRunnerDistroConfigOverride(t *testing.T) {
})
}
}

func TestValidateBuildArchitecture(t *testing.T) {
imageConfig := &projectconfig.ImageConfig{
Name: "gen1",
Architectures: []string{projectconfig.ImageArchitectureX86_64},
}

require.NoError(t, validateBuildArchitecture(
imageConfig,
ImageArchX86_64,
"arm64",
))
require.NoError(t, validateBuildArchitecture(
imageConfig,
ImageArchDefault,
"amd64",
))

err := validateBuildArchitecture(imageConfig, ImageArchAarch64, "amd64")
require.ErrorContains(t, err, "image `gen1` does not support architecture `aarch64`")

err = validateBuildArchitecture(imageConfig, ImageArchDefault, "arm64")
require.ErrorContains(t, err, "image `gen1` does not support architecture `aarch64`")

err = validateBuildArchitecture(imageConfig, ImageArchDefault, "riscv64")
require.ErrorContains(t, err, "unsupported host architecture `riscv64`")
}
20 changes: 16 additions & 4 deletions internal/app/azldev/cmds/image/list.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,13 @@ type ImageListResult struct {
// display.
CapabilitiesSummary string `json:"-" table:"Capabilities"`

// Architectures lists the architectures supported by this image, as declared
// explicitly in its config (there is no project-wide default).
Architectures []string `json:"architectures" table:"-"`

// ArchitecturesSummary is a comma-separated summary for table display.
ArchitecturesSummary string `json:"-" table:"Architectures"`

// Tests holds the test configuration for this image, matching the original config
// structure.
Tests *projectconfig.ImageTestsConfig `json:"tests,omitempty" table:"-"`
Expand Down Expand Up @@ -135,10 +142,15 @@ func ListImages(env *azldev.Env, options *ListImageOptions) ([]ImageListResult,
Description: imageConfig.Description,
Capabilities: imageConfig.Capabilities,
CapabilitiesSummary: strings.Join(imageConfig.Capabilities.EnabledNames(), ", "),
Tests: imageConfig.Tests,
TestsSummary: strings.Join(imageConfig.TestNames(), ", "),
Publish: imageConfig.Publish,
PublishSummary: strings.Join(imageConfig.Publish.Channels, ", "),
Architectures: imageConfig.Architectures,
ArchitecturesSummary: strings.Join(
imageConfig.Architectures,
", ",
),
Tests: imageConfig.Tests,
TestsSummary: strings.Join(imageConfig.TestNames(), ", "),
Publish: imageConfig.Publish,
PublishSummary: strings.Join(imageConfig.Publish.Channels, ", "),
Definition: ImageDefinitionResult{
Type: string(imageConfig.Definition.DefinitionType),
Path: imageConfig.Definition.Path,
Expand Down
30 changes: 26 additions & 4 deletions internal/app/azldev/cmds/image/list_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,16 +46,18 @@ func TestListImages_AllImages(t *testing.T) {
testEnv := testutils.NewTestEnv(t)
testEnv.Config.Images = map[string]projectconfig.ImageConfig{
"image-a": {
Name: "image-a",
Description: "Image A description",
Name: "image-a",
Description: "Image A description",
Architectures: []string{"x86_64", "aarch64"},
Definition: projectconfig.ImageDefinition{
DefinitionType: projectconfig.ImageDefinitionTypeKiwi,
Path: "/path/to/image-a.kiwi",
},
},
"image-b": {
Name: "image-b",
Description: "Image B description",
Name: "image-b",
Description: "Image B description",
Architectures: []string{"x86_64"},
Definition: projectconfig.ImageDefinition{
DefinitionType: projectconfig.ImageDefinitionTypeKiwi,
Path: "/path/to/image-b.kiwi",
Expand All @@ -72,11 +74,31 @@ func TestListImages_AllImages(t *testing.T) {
// Results should be sorted alphabetically by name.
assert.Equal(t, "image-a", results[0].Name)
assert.Equal(t, "Image A description", results[0].Description)
assert.Equal(t, []string{"x86_64", "aarch64"}, results[0].Architectures)
assert.Equal(t, "x86_64, aarch64", results[0].ArchitecturesSummary)
assert.Equal(t, "kiwi", results[0].Definition.Type)
assert.Equal(t, "/path/to/image-a.kiwi", results[0].Definition.Path)

assert.Equal(t, "image-b", results[1].Name)
assert.Equal(t, "Image B description", results[1].Description)
assert.Equal(t, []string{"x86_64"}, results[1].Architectures)
assert.Equal(t, "x86_64", results[1].ArchitecturesSummary)
}

func TestListImages_ArchitecturesPerImage(t *testing.T) {
testEnv := testutils.NewTestEnv(t)
testEnv.Config.Images = map[string]projectconfig.ImageConfig{
"gen1": {
Name: "gen1",
Architectures: []string{"x86_64"},
},
}

results, err := image.ListImages(testEnv.Env, &image.ListImageOptions{})
require.NoError(t, err)
require.Len(t, results, 1)
assert.Equal(t, []string{"x86_64"}, results[0].Architectures)
assert.Equal(t, "x86_64", results[0].ArchitecturesSummary)
}

func TestListImages_WithCapabilitiesAndTests(t *testing.T) {
Expand Down
Loading
Loading