Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 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
271 changes: 163 additions & 108 deletions docs/imagecustomizer/api/cosi.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,13 @@ nav_order: 3

# Composable Operating System Image (COSI) Specification

## Revision Summary

| Revision | Spec Date |
|----------|-------------|
| 1.1 | TBD |
| 1.0 | 2024-10-09 |

## COSI File Format

The COSI file MUST be an uncompressed tarball. The file extension SHOULD be `.cosi`.
Expand All @@ -14,101 +21,110 @@ The COSI file MUST be an uncompressed tarball. The file extension SHOULD be `.co
The tarball MUST contain the following files:

- `metadata.json`: A JSON file that contains the metadata of the COSI file.
- Partition image files in the folder `images`: The actual partition images
that will be used to install the OS.
- Filesystem image files in the folder `images/`: The actual filesystem images
that Trident will use to install the OS.

To allow for future extensions, the tarball MAY contain other files, but Trident
Comment thread
elainezhao1 marked this conversation as resolved.
Outdated
MUST ignore them. The tarball SHOULD NOT contain any extra files that will not
be used by Trident.

### Layout

If the tarball contains other files, readers MUST ignore them. A writer SHOULD NOT
add any other files to the tarball.
The tarball MUST NOT have a common root directory. The metadata file MUST be at
the root of the tarball. If it were extracted with a standard `tar` invocation,
the metadata file would be placed in the current directory.

The tarball MUST NOT have a common root directory. The `metadata.json` file and the
`images` directory MUST be in the root directory of the tarball.
The metadata file SHOULD, be placed at the beginning of the tarball to allow for
Comment thread
elainezhao1 marked this conversation as resolved.
Outdated
quick access to the metadata without having to traverse the entire tarball.

The metadata file SHOULD be placed at the beginning of the tarball to allow for
quick access to the metadata without needing to traverse the entire tarball.
### Partition Image Files

## Partition Image Files
The partition image files are the actual images that Trident will use to install
the OS. These MUST be raw partition images.

The partition image files MUST be raw partition images that are compressed using ZSTD
compression.
The image files SHOULD be compressed. They SHOULD use ZSTD compression. Trident
only supports ZSTD-compressed images at the time of writing (2024-09-25), but
that could change in the future. Not using ZSTD-compressed images will result in
Trident failing to install the OS.

All partition image files MUST be in the `images` directory or one of its
subdirectories.
They MUST be located in a directory called `images/` inside the tarball. They
MAY be placed in subdirectories of `images/` to organize them. Trident MUST be
able to handle images in subdirectories.

## Metadata JSON File
### Metadata JSON File

The metadata file MUST be named `metadata.json` and MUST be a valid JSON file.
The metadata file MUST be named `metadata.json` and MUST be at the root of the
tarball. The metadata file MUST be a valid JSON file.

## Metadata JSON Schema
#### Schema

### Root Object
##### Root Object

The metadata file MUST contain a JSON object with the following fields:

| Field | Type | Required | Description |
| ------------ | -------------------------------------- | -------- | ------------------------------------------------------ |
| `version` | string `MAJOR.MINOR` | Yes | The version of the metadata schema. MUST be `1.0`. |
| `osArch` | [OsArchitecture](#osarchitecture-enum) | Yes | The CPU architecture of the OS. |
| `osRelease` | string | Yes | The contents of OS's `/etc/os-release` file. |
| `images` | [Image](#image-object)[] | Yes | Metadata of partition images that contain filesystems. |
| `osPackages` | [OsPackage](#ospackage-object)[] | No | The list of packages installed in the OS. |
| `id` | UUID (string, case insensitive) | No | A unique identifier for the COSI file. |

If the object contains other fields, readers MUST ignore them. A writer SHOULD NOT
add any other files to the object.

### `Image` Object

| Field | Type | Required | Description |
| ------------ | ------------------------------------ | -------- | ----------------------------------------- |
| `image` | [ImageFile](#imagefile-object) | Yes | Details of the image file in the tarball. |
| `mountPoint` | string | Yes | The mount point of the partition. |
| `fsType` | string | Yes | The filesystem type of the partition. [1] |
| `fsUuid` | string | Yes | The UUID of the filesystem. |
| `partType` | UUID (string, case insensitive) | Yes | The GPT partition type. [2] [3] [4] |
| `verity` | [VerityConfig](#verityconfig-object) | No | The verity metadata of the partition. |
| Field | Type | Added in | Required | Description |
| ------------ | -------------------------------------- | -------- | --------------- | ------------------------------------------------ |
| `version` | string `MAJOR.MINOR` | 1.0 | Yes (since 1.0) | The version of the metadata schema. |
| `osArch` | [OsArchitecture](#osarchitecture-enum) | 1.0 | Yes (since 1.0) | The architecture of the OS. |
| `osRelease` | string | 1.0 | Yes (since 1.0) | The contents of `/etc/os-release` verbatim. |
| `images` | [Filesystem](#filesystem-object)[] | 1.0 | Yes (since 1.0) | Filesystem metadata. |
| `osPackages` | [OsPackage](#ospackage-object)[] | 1.0 | Yes (since 1.1) | The list of packages installed in the OS. |
| `id` | UUID (string, case insensitive) | 1.0 | No | A unique identifier for the COSI file. |

If the object contains other fields, readers MUST ignore them. A writer SHOULD
NOT add any other files to the object.

##### `Filesystem` Object

This object carries information about a filesystem and the partition it comes
from in a virtual disk.

| Field | Type | Added in | Required | Description |
| ------------ | ------------------------------------ | -------- | ---------------- | ----------------------------------------- |
| `image` | [ImageFile](#imagefile-object) | 1.0 | Yes (since 1.0) | Details of the image file in the tarball. |
| `mountPoint` | string | 1.0 | Yes (since 1.0) | The mount point of the filesystem. |
| `fsType` | string | 1.0 | Yes (since 1.0) | The filesystem's type. [1] |
| `fsUuid` | string | 1.0 | Yes (since 1.0) | The UUID of the filesystem. [2] |
| `partType` | UUID (string, case insensitive) | 1.0 | Yes (since 1.0) | The GPT partition type. [3] [4] [5] |
| `verity` | [VerityConfig](#verityconfig-object) | 1.0 | Conditionally[6] | The verity metadata of the filesystem. |

_Notes:_

- **[1]** It MUST use the name recognized by the Linux kernel. For example, `ext4` for
- **[1]** It MUST use the name recognized by the kernel. For example, `ext4` for
ext4 filesystems, `vfat` for FAT32 filesystems, etc.

- **[2]** It MUST be a UUID defined by the [Discoverable
Partition Specification
- **[2]** It MUST be unique across all filesystems in the COSI tarball.
Additionally, volumes in an A/B volume pair MUST have unique filesystem UUIDs.
- **[3]** It MUST be a UUID defined by the [Discoverable Partition Specification
(DPS)](https://uapi-group.org/specifications/specs/discoverable_partitions_specification/)
when the applicable type exists in the DPS. Other partition types MAY be
used for types not defined in DPS (e.g. Windows partitions).

- **[3]** The EFI Sytem Partition (ESP) MUST be identified with the UUID
- **[4]** The EFI Sytem Partition (ESP) MUST be identified with the UUID
established by the DPS: `c12a7328-f81f-11d2-ba4b-00a0c93ec93b`.

- **[4]** Should default to `0fc63daf-8483-4772-8e79-3d69d8477de4` (Generic
- **[5]** Should default to `0fc63daf-8483-4772-8e79-3d69d8477de4` (Generic
Linux Data) if the partition type cannot be determined.
- **[6]** The `verity` field MUST be specified if the OS is configured to open this
filesystem with `dm-verity`. Otherwise, it MUST be omitted OR set to `null`.

### `VerityConfig` Object
##### `VerityConfig` Object

The `VerityConfig` object contains information required to set up a verity
device on top of a data partition.

| Field | Type | Required | Description |
| ---------- | ------------------------------ | -------- | -------------------------------------------------------- |
| `image` | [ImageFile](#imagefile-object) | Yes | Details of the hash partition image file in the tarball. |
| `roothash` | string | Yes | Verity root hash. |
device on top of a data device.

### `ImageFile` Object
| Field | Type | Added in | Required | Description |
| ---------- | ------------------------------ | -------- | --------------- | -------------------------------------------------------- |
| `image` | [ImageFile](#imagefile-object) | 1.0 | Yes (since 1.0) | Details of the hash partition image file in the tarball. |
| `roothash` | string | 1.0 | Yes (since 1.0) | Verity root hash. |

| Field | Type | Required | Description |
| ------------------ | ------ | -------- | ----------------------------------------------------------------------------------------- |
| `path` | string | Yes | Absolute path of the compressed image file inside the tarball. MUST start with `images/`. |
| `compressedSize` | number | Yes | Size of the compressed image in bytes. |
| `uncompressedSize` | number | Yes | Size of the raw uncompressed image in bytes. |
| `sha384` | string | No[5] | SHA-384 hash of the compressed hash image. |
##### `ImageFile` Object

_Notes:_
| Field | Type | Added in | Required | Description |
| ------------------ | ------ | -------- | --------------- | ----------------------------------------------------------------------------------------- |
| `path` | string | 1.0 | Yes (since 1.0) | Absolute path of the compressed image file inside the tarball. MUST start with `images/`. |
| `compressedSize` | number | 1.0 | Yes (since 1.0) | Size of the compressed image in bytes. |
| `uncompressedSize` | number | 1.0 | Yes (since 1.0) | Size of the raw uncompressed image in bytes. |
| `sha384` | string | 1.0 | Yes (since 1.1) | SHA-384 hash of the compressed hash image. |

- **[5]** The `sha384` field is optional, but it is RECOMMENDED to include it for
integrity verification.

### `OsArchitecture` Enum
##### `OsArchitecture` Enum

The `osArch` field in the root object MUST be a string that represents the
architecture of the OS. The following table lists the valid values for the
Expand All @@ -119,28 +135,29 @@ architecture of the OS. The following table lists the valid values for the
| `x86_64` | AMD64 or Intel 64-bit architecture. |
| `arm64` | ARM 64-bit architecture. |

### `OsPackage` Object
_Note:_ The `osArch` field uses the names reported by `uname -m` for consistency.
The `osArch` field is case-insensitive.

##### `OsPackage` Object

When present, the `osPackages` field in the root object MUST contain an array of
`OsPackage` objects. Each object represents a package installed in the OS.
The `osPackages` field in the root object MUST contain an array of `OsPackage`
objects. Each object represents a package installed in the OS.

A reader MAY use this field to determine if the OS is missing any packages that are
required for how the user intends to use the OS image.
| Field | Type | Added in | Required | Description |
| --------- | ------ | -------- | --------------- | ------------------------------------- |
| `name` | string | 1.0 | Yes (since 1.0) | The name of the package. |
| `version` | string | 1.0 | Yes (since 1.0) | The version of the package installed. |
| `release` | string | 1.0 | Yes (since 1.1) | The release of the package. |
| `arch` | string | 1.0 | Yes (since 1.1) | The architecture of the package. |

| Field | Type | Required | Description |
| --------- | ------ | -------- | ------------------------------------- |
| `name` | string | Yes | The name of the package. |
| `version` | string | Yes | The version of the package installed. |
| `release` | string | No | The release number of the package. |
| `arch` | string | No | The CPU architecture of the package. |

### Samples
#### Samples

#### Simple Image
##### Simple Image

```json
{
"version": "1.0",
"version": "1.1",
"images": [
{
"image": {
Expand Down Expand Up @@ -169,15 +186,36 @@ required for how the user intends to use the OS image.
"verity": null
}
],
"osRelease": "NAME=\"Microsoft Azure Linux\"\nVERSION=\"3.0.20240824\"\nID=azurelinux\nVERSION_ID=\"3.0\"\nPRETTY_NAME=\"Microsoft Azure Linux 3.0\"\nANSI_COLOR=\"1;34\"\nHOME_URL=\"https://aka.ms/azurelinux\"\nBUG_REPORT_URL=\"https://aka.ms/azurelinux\"\nSUPPORT_URL=\"https://aka.ms/azurelinux\"\n"
"osRelease": "NAME=\"Microsoft Azure Linux\"\nVERSION=\"3.0.20240824\"\nID=azurelinux\nVERSION_ID=\"3.0\"\nPRETTY_NAME=\"Microsoft Azure Linux 3.0\"\nANSI_COLOR=\"1;34\"\nHOME_URL=\"https://aka.ms/azurelinux\"\nBUG_REPORT_URL=\"https://aka.ms/azurelinux\"\nSUPPORT_URL=\"https://aka.ms/azurelinux\"\n",
"osPackages": [
{
"name": "bash",
"version": "5.1.8",
"release": "1.azl3",
"arch": "x86_64"
},
{
"name": "coreutils",
"version": "8.32",
"release": "1.azl3",
"arch": "x86_64"
},
{
"name": "systemd",
"version": "255",
"release": "20.azl3",
"arch": "x86_64"
},
// More packages...
]
}
```

#### Verity Image
##### Verity Image with UKI

```json
{
"version": "1.0",
"version": "1.1",
"images": [
{
"image": {
Expand All @@ -202,31 +240,13 @@ required for how the user intends to use the OS image.
},
// More images...
],
"osRelease": "NAME=\"Microsoft Azure Linux\"\nVERSION=\"3.0.20240824\"\nID=azurelinux\nVERSION_ID=\"3.0\"\nPRETTY_NAME=\"Microsoft Azure Linux 3.0\"\nANSI_COLOR=\"1;34\"\nHOME_URL=\"https://aka.ms/azurelinux\"\nBUG_REPORT_URL=\"https://aka.ms/azurelinux\"\nSUPPORT_URL=\"https://aka.ms/azurelinux\"\n"
}
```

#### Packages

```json
{
"version": "1.0",
"images": [
// Images...
],
"osRelease": "<OS_RELEASE>",
"osRelease": "NAME=\"Microsoft Azure Linux\"\nVERSION=\"3.0.20240824\"\nID=azurelinux\nVERSION_ID=\"3.0\"\nPRETTY_NAME=\"Microsoft Azure Linux 3.0\"\nANSI_COLOR=\"1;34\"\nHOME_URL=\"https://aka.ms/azurelinux\"\nBUG_REPORT_URL=\"https://aka.ms/azurelinux\"\nSUPPORT_URL=\"https://aka.ms/azurelinux\"\n",
"osPackages": [
{
"name": "bash",
"version": "5.1.8"
},
{
"name": "coreutils",
"version": "8.32"
},
{
"name": "systemd",
"version": "255"
"version": "255",
"release": "20.azl3",
"arch": "x86_64"
},
// More packages...
]
Expand All @@ -240,16 +260,51 @@ required for how the user intends to use the OS image.
- Tar is simple and ubiquitous. It is easy to create and extract tarballs on
virtually any platform. There are native libraries for virtually every
programming language to handle tarballs, including Rust and Go.
- Tar is a super simple tape format. It is just a stream of files with metadata
at the beginning. This makes it easy to read and write.

**Why an uncompressed tarball?**

- This allows the metadata file to be easily read without needing to decompress and
extract the entire tarball. Also, compressing the tarball doesn't provide any
meaningful size reductions since the partition images are all compressed individually.
- The images SHOULD be compressed, and other than that the file should be pretty
light-weight. Compressing the entire tarball does not yield a significant size
reduction, if at all. This also allows us to read the metadata without having
to extract the entire tarball.

**Why not ZIP?**

- ZIP is more complex than tar. It has more features, notably an index at the
end of the file. However, to compute the hash of the file, we need to read it
through, anyway, so we can index the file as we read it. Even in cases where
we don't need to compute the hash, to take full advantage of the index, we
would need to implement our own ZIP reader.
- ZSTD support in ZIP is not very
widespread.

**Why not use a custom format?**

- Making a custom format MAY help us achieve greater performance is some edge
cases, specifically network streaming. However, the complexity of creating and
maintaining a custom format outweighs the benefits. Tar is simple and good
enough for our needs.

**Why not use VHD or VHDX?**

- VHD and VHDX are complex formats that are not designed for our use case. They
are designed to be used as virtual disks, not as a simple container for
partition images. They are also not as portable as tarballs.
- They do not have a standard way to store metadata. The spec does include some
empty space reserved for future expansion, but using it would require us to
implement our own fork of the VHD/VHDX spec.

**What about a VHD+Metadata?**

- Putting the metadata in a separate file would defeat the purpose of having a
single file.

**What other formats were considered?**

- We considered using a custom format, but the complexity of creating and
maintaining a custom format outweighs the benefits.
- SquashFS was considered, but it would only change the container around the
filesystems images. When considering only the container, there was no real
practical benefit to using SquashFS over Tar.
Loading
Loading