Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Image Metadata Proposal #95

Merged
merged 4 commits into from
Sep 29, 2022
Merged
Changes from 3 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
83 changes: 83 additions & 0 deletions proposals/image-metadata.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
# Image Metadata

## Goal

Record dev container config and feature metadata in prebuilt images, such that, the image and the built-in features can be used with a devcontainer.json (image-, Dockerfile- or Docker Compose-based) that does not repeat the dev container config or feature metadata. Other tools should be able to record the same metadata without necessarily using features themselves.

Current dev container config that can be recorded in the image: `mounts`, `onCreateCommand`, `updateContentCommand`, `postCreateCommand`, `postStartCommand`, `postAttachCommand`, `customizations`, `remoteUser`, `userEnvProbe`, `remoteEnv`, `containerEnv`, `overrideCommand`, `portsAttributes`, `otherPortsAttributes`, `forwardPorts`, `shutdownAction`, `updateRemoteUserUID` and `hostRequirements`.

Current feature metadata relevant for using the feature when it is already part of the image: `mounts`, `init`, `privileged`, `capAdd`, `securityOpt`, `entrypoint` and `customizations`.

We can add to these lists as we add more properties to the dev container configuration and the feature metadata.

## Proposal

We propose to add metadata to the image with the following structure using one entry per feature and devcontainer.json (see table below for the full list):

```
[
{
"id"?: string,
"init"?: boolean,
"privileged"?: boolean,
"capAdd"?: string[],
"securityOpt"?: string[],
"entrypoint"?: string,
"mounts"?: [],
...
"customizations"?: {
...
}
},
...
]
```

To simplify adding this metadata for other tools, we also support having a single top-level object with the same properties.

The metadata is added to the image as a `devcontainer.metadata` label with a JSON string value representing the above array or single object.

chrmarti marked this conversation as resolved.
Show resolved Hide resolved
## Merge Logic

To apply the metadata together with a user's devcontainer.json at runtime the following merge logic by property is used. The table also notes which properties are currently supported coming from the devcontainer.json and which from the feature metadata, this will change over time as we add more properties.

| Property | Type/Format | Merge Logic | devcontainer.json | Feature Metadata |
| -------- | ----------- | ----------- | :---------------: | :--------------: |
| `id` | E.g., `ghcr.io/devcontainers/features/node:1` | Not merged. | | x |
| `init` | `boolean` | `true` if at least one is `true`, `false` otherwise. | | x |
| `privileged` | `boolean` | `true` if at least one is `true`, `false` otherwise. | | x |
| `capAdd` | `string[]` | Union of all `capAdd` arrays without duplicates. | | x |
| `securityOpt` | `string[]` | Union of all `securityOpt` arrays without duplicates. | | x |
| `entrypoint` | `string` | Collected list of all entrypoints. | | x |
| `mounts` | `(string \| { type, src, dst })[]` | Collected list of all mountpoints. Conflicts: Last source wins. | x | x |
chrmarti marked this conversation as resolved.
Show resolved Hide resolved
| `onCreateCommand` | `string \| string[]` | Collected list of all onCreateCommands. | x | |
| `updateContentCommand` | `string \| string[]` | Collected list of all updateContentCommands. | x | |
| `postCreateCommand` | `string \| string[]` | Collected list of all postCreateCommands. | x | |
| `postStartCommand` | `string \| string[]` | Collected list of all postStartCommands. | x | |
| `postAttachCommand` | `string \| string[]` | Collected list of all postAttachCommands. | x | |
| `waitFor` | enum | Last value wins. | x | |
| `customizations` | Object of tool-specific customizations. | Merging is left to the tools. | x | x |
| `containerUser` | `string` | Last value wins. | x | |
| `remoteUser` | `string` | Last value wins. | x | |
chrmarti marked this conversation as resolved.
Show resolved Hide resolved
| `userEnvProbe` | `string` (enum) | Last value wins. | x | |
| `remoteEnv` | Object of strings. | Per variable, last value wins. | x | |
| `containerEnv` | Object of strings. | Per variable, last value wins. | x | |
chrmarti marked this conversation as resolved.
Show resolved Hide resolved
| `overrideCommand` | `boolean` | Last value wins. | x | |
| `portsAttributes` | Map of ports to attributes. | Per port (not per port attribute), last value wins. | x | |
| `otherPortsAttributes` | Port attributes. | Last value wins (not per port attribute). | x | |
| `forwardPorts` | `(number \| string)[]` | Union of all ports without duplicates. Last one wins (when mapping changes). | x | |
| `shutdownAction` | `string` (enum) | Last value wins. | x | |
| `updateRemoteUserUID` | `boolean` | Last value wins. | x | |
| `hostRequirements` | `cpus`, `memory`, `storage` | Max value wins. | x | |

Variables in string values will be substituted at the time the value is applied. When the order matters, the devcontainer.json is considered last.

## Notes
chrmarti marked this conversation as resolved.
Show resolved Hide resolved

- Passing the label as a `LABEL` instruction in the Dockerfile:
- The size limit on Dockerfiles is around 1.3MB. The line length is limited to 65k characters.
- Using one line per feature should allow for making full use of these limits.
- Passing the label as a command line argument:
- There is no size limit documented for labels, but the daemon returns an error when the request header is >500kb.
- The 500kb limit is shared, so we cannot use a second label in the same build to avoid it.
- If/when this becomes an issue we could embed the metadata as a file in the image (e.g., with a label indicating it).