Documentation
¶
Overview ¶
Package config defines the parsed and resolved devcontainer.json configuration types.
The package separates the wire format (rawConfig, internal) from the public ResolvedConfig produced by Resolve. See design/resolved-config.md for the rationale and the full type specification.
Index ¶
- func BoolOr(p *bool, def bool) bool
- func DecodeMounts(data json.RawMessage) ([]Mount, []Warning, error)
- func DevcontainerID(localWorkspaceFolder, configPath string) string
- func MergeMetadata(cfg *ResolvedConfig, subCtx SubstitutionContext, layers []FeatureMetadata)
- func SortedKeys[V any](m map[string]V) []string
- type BindOptions
- type BuildSource
- type Command
- type ComposeSource
- type ConfigInvalidError
- type ConfigParseError
- type FeatureMetadata
- type FeatureOption
- type FeatureSourceKind
- type GPURequirement
- type HostRequirements
- type ImageSource
- type LifecycleCommand
- type LifecycleCommands
- type LifecyclePhase
- type Mount
- type MountType
- type PortAttributes
- type PortSpec
- type ResolveInput
- type ResolvedConfig
- type ResolvedFeature
- type ShutdownAction
- type Source
- type SourceKind
- type SubstitutionContext
- type TmpfsOptions
- type UserEnvProbe
- type VolumeDriverConfig
- type VolumeOptions
- type Warning
- type WarningCode
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func BoolOr ¶
BoolOr returns *p when non-nil, else def. Helper for ResolvedConfig optional-bool field consumers.
func DecodeMounts ¶
func DecodeMounts(data json.RawMessage) ([]Mount, []Warning, error)
DecodeMounts is the exported alias of decodeMounts for use by the feature package's metadata-label parser. Same semantics.
func DevcontainerID ¶
DevcontainerID returns a stable identifier for a workspace, derived deterministically from (localWorkspaceFolder, configPath). Same inputs always yield the same id, distinct inputs (with overwhelming probability) yield distinct ids.
The id is 16 lowercase base32 characters (80 bits) — collision-resistant for any practical workspace count, charset-compliant for use in container names, network names, and compose project names.
func MergeMetadata ¶
func MergeMetadata(cfg *ResolvedConfig, subCtx SubstitutionContext, layers []FeatureMetadata)
MergeMetadata folds image-metadata layers into cfg following spec last-write-wins semantics.
`layers` is the chain in install order:
- base image label entries (one per feature originally installed, plus the final resolved-config entry from the prior build)
- each newly-installed feature's parsed devcontainer-feature.json
The user's devcontainer.json is *already* in cfg by the time this is called (Resolve has populated it). Per spec, user-supplied values win over feature metadata, and feature metadata wins over base image metadata. So:
- Scalars (string / *bool / typed string aliases): only filled in when cfg has no explicit value (empty string, nil pointer). Within `layers`, later entries beat earlier ones.
- Maps (containerEnv, remoteEnv): unioned across all layers, then user values overlaid on top — user wins on key collisions, but base/feature keys the user did not set survive.
- String slices (capAdd, securityOpt): unioned with deduplication. Order: base layers first, then features, then user (preserves install order; dedup keeps the first occurrence).
- Mounts: appended in chain order; duplicates by Target are removed keeping the LAST occurrence (so user-supplied mount targets win).
- Lifecycle hooks: appended per phase in chain order, preserving any user-supplied command at the end. Empty hooks are skipped.
- HostRequirements: filled if cfg.HostRequirements is nil; otherwise cfg keeps its value (user wins).
- Customizations: not merged here — each tool reads its own namespace and decides how to combine layers. We pass the user's map through unchanged.
`subCtx` supplies values for ${devcontainerId}, ${localWorkspaceFolder}, ${containerWorkspaceFolder}, ${localEnv:*} placeholders that appear in layer-contributed string fields (mount sources, env values, lifecycle commands, …). Variables in the user's devcontainer.json are already substituted by ResolveBytes; this pass extends the invariant to feature- and base-image-contributed strings so they reach the runtime without literal ${...} tokens. Pass a zero SubstitutionContext to skip substitution (useful for tests with no placeholders).
Idempotent: calling MergeMetadata with the same layers twice produces the same result.
func SortedKeys ¶
SortedKeys returns the keys of m in lexicographic order. Useful when rendering map-typed fields (e.g. ContainerEnv, RemoteEnv) deterministically for logging or test fixtures, since Go map iteration order is unspecified.
Types ¶
type BindOptions ¶
type BuildSource ¶
type BuildSource struct {
Dockerfile string
Context string
Args map[string]string
Target string
CacheFrom []string
Options map[string]string
}
func (*BuildSource) Kind ¶
func (*BuildSource) Kind() SourceKind
type Command ¶
Command is a single executable invocation in either shell or exec form. Exactly one of Shell / Exec is populated.
type ComposeSource ¶
func (*ComposeSource) Kind ¶
func (*ComposeSource) Kind() SourceKind
type ConfigInvalidError ¶
ConfigInvalidError indicates that a parsed config violates a structural rule that prevents producing a ResolvedConfig (e.g. neither image, build, nor dockerComposeFile is set).
func (*ConfigInvalidError) Error ¶
func (e *ConfigInvalidError) Error() string
type ConfigParseError ¶
ConfigParseError indicates that a devcontainer.json file could not be parsed (JSONC syntax invalid, schema mismatch, etc.).
func (*ConfigParseError) Error ¶
func (e *ConfigParseError) Error() string
func (*ConfigParseError) Unwrap ¶
func (e *ConfigParseError) Unwrap() error
type FeatureMetadata ¶
type FeatureMetadata struct {
ID string
Version string
Name string
Description string
DocumentationURL string
LicenseURL string
Options map[string]FeatureOption
// Mergeable surface (devpod parity). Empty / nil means "unset".
RemoteUser string
ContainerUser string
UserEnvProbe UserEnvProbe
WaitFor LifecyclePhase
ShutdownAction ShutdownAction
UpdateRemoteUserUID *bool
ContainerEnv map[string]string
RemoteEnv map[string]string
Mounts []Mount
Init *bool
Privileged *bool
OverrideCommand *bool
CapAdd []string
SecurityOpt []string
Entrypoint string
HostRequirements *HostRequirements
InstallsAfter []string
DependsOn map[string]map[string]any
OnCreateCommand LifecycleCommand
UpdateContentCommand LifecycleCommand
PostCreateCommand LifecycleCommand
PostStartCommand LifecycleCommand
PostAttachCommand LifecycleCommand
Customizations map[string]json.RawMessage
}
FeatureMetadata is one layer of the devcontainer.metadata chain. It represents either the parsed devcontainer-feature.json contributed by a feature, OR a single entry in the image's devcontainer.metadata label (which may be a base image's prior layer, a feature's contribution, or the resolved-config "final" entry written by a previous build).
Every field is optional; the merge stage (MergeMetadata) treats empty strings, nil pointers, and zero-length slices/maps as "this layer did not contribute a value".
ID/Version/Name/Description/DocumentationURL/LicenseURL/Options/ InstallsAfter/DependsOn are feature-only — they have no merge semantics and are ignored when the layer originates from a label entry.
type FeatureOption ¶
type FeatureOption struct {
Type string // "string" | "boolean"
Default any
Enum []any
Proposals []any
Description string
}
FeatureOption is one option declared by a feature, used to apply defaults and validate user-supplied values.
type FeatureSourceKind ¶
type FeatureSourceKind string
const ( FeatureSourceOCI FeatureSourceKind = "oci" FeatureSourceHTTPS FeatureSourceKind = "https" FeatureSourceLocal FeatureSourceKind = "local" )
type GPURequirement ¶
type GPURequirement struct {
Optional bool
}
type HostRequirements ¶
type HostRequirements struct {
CPUs int
Memory string
Storage string
GPU *GPURequirement
}
HostRequirements is parsed and surfaced; v1 does not enforce.
type ImageSource ¶
type ImageSource struct {
Image string
}
func (*ImageSource) Kind ¶
func (*ImageSource) Kind() SourceKind
type LifecycleCommand ¶
LifecycleCommand is one phase's command, in either single or parallel-named form. At most one of Single / Parallel is populated; both empty means the phase is unconfigured and must be skipped.
func DecodeLifecycleCommand ¶
func DecodeLifecycleCommand(data json.RawMessage) (LifecycleCommand, error)
DecodeLifecycleCommand is the exported alias of decodeLifecycleCommand for use by the feature package's metadata-label parser. Same semantics: accepts string | []string | object (parallel-named).
func (LifecycleCommand) IsEmpty ¶
func (l LifecycleCommand) IsEmpty() bool
type LifecycleCommands ¶
type LifecycleCommands struct {
Initialize []LifecycleCommand
OnCreate []LifecycleCommand
UpdateContent []LifecycleCommand
PostCreate []LifecycleCommand
PostStart []LifecycleCommand
PostAttach []LifecycleCommand
}
LifecycleCommands holds the resolved command(s) for each phase. Each phase is a list because the metadata-merge pipeline (base image label → each feature → user devcontainer.json) can contribute hooks that all run in sequence per-phase, per spec.
Initialize is host-side and only ever populated from the user's devcontainer.json today, but is shaped as a slice for symmetry.
type LifecyclePhase ¶
type LifecyclePhase string
LifecyclePhase names the spec lifecycle phases plus initialize.
const ( LifecycleInitialize LifecyclePhase = "initialize" LifecycleOnCreate LifecyclePhase = "onCreate" LifecycleUpdateContent LifecyclePhase = "updateContent" LifecyclePostCreate LifecyclePhase = "postCreate" LifecyclePostStart LifecyclePhase = "postStart" LifecyclePostAttach LifecyclePhase = "postAttach" )
type Mount ¶
type Mount struct {
Type MountType
Source string
Target string
ReadOnly bool
BindOptions *BindOptions
VolumeOptions *VolumeOptions
TmpfsOptions *TmpfsOptions
}
Mount is the normalized object form of a mount entry. Spec CSV strings are parsed into this shape during merge.
type PortAttributes ¶
type ResolveInput ¶
type ResolveInput struct {
// LocalWorkspaceFolder is the absolute host path containing the project.
LocalWorkspaceFolder string
// ConfigPath is the absolute path of the source devcontainer.json.
// Used as Warning.Source and to resolve relative paths in build/compose.
ConfigPath string
// DevcontainerID is the stable workspace id. Callers may use the default
// scheme via DevcontainerID(local, config) or supply their own.
DevcontainerID string
// LocalEnv is the host environment for ${localEnv:*} resolution. Callers
// typically pass the result of os.Environ() turned into a map.
LocalEnv map[string]string
}
ResolveInput is the contextual data needed to produce a ResolvedConfig from a parsed devcontainer.json document.
type ResolvedConfig ¶
type ResolvedConfig struct {
DevcontainerID string
Source Source
Name string
LocalWorkspaceFolder string
ContainerWorkspaceFolder string
WorkspaceMount *Mount
ContainerUser string
RemoteUser string
UpdateRemoteUserUID *bool
UserEnvProbe UserEnvProbe
ContainerEnv map[string]string
RemoteEnv map[string]string
Mounts []Mount
RunArgs []string
Init *bool
Privileged *bool
CapAdd []string
SecurityOpt []string
OverrideCommand *bool
ShutdownAction ShutdownAction
Features []ResolvedFeature
Lifecycle LifecycleCommands
WaitFor LifecyclePhase
// SecretsCommand is a host-side hook that runs before container start
// (analogous to initializeCommand) and whose stdout is parsed as
// key=value lines and merged into the container's environment. Unlike
// the lifecycle phases, it is not contributed by feature/base-image
// metadata layers — only the user's devcontainer.json sources it —
// so it is a single LifecycleCommand rather than a slice. Empty
// when devcontainer.json has no `secretsCommand`.
SecretsCommand LifecycleCommand
ForwardPorts []PortSpec
PortsAttributes map[string]PortAttributes
OtherPortsAttributes *PortAttributes
HostRequirements *HostRequirements
Customizations map[string]json.RawMessage
Warnings []Warning
}
ResolvedConfig is the merged and host-substituted devcontainer configuration produced by Resolve. Treat as read-only after Resolve returns; mutate only via DefensiveCopy.
String fields may still contain unresolved ${containerEnv:*} placeholders; see HasPendingSubstitutions.
func ResolveBytes ¶
func ResolveBytes(src []byte, input ResolveInput) (*ResolvedConfig, error)
ResolveBytes parses the supplied devcontainer.json bytes and produces a merged, host-substituted ResolvedConfig. Image-label metadata and feature resolution are stubbed in this milestone; see PRD §13 / status.md for what still lands in M3.
func (*ResolvedConfig) Finalize ¶
func (c *ResolvedConfig) Finalize()
Finalize applies spec defaults to optional fields that the user (and any merged metadata layer) left unset. Call after Resolve and after the metadata-merge pipeline so that base-image / feature metadata can still contribute values before defaults kick in.
Defaults applied:
- OverrideCommand: true
- UserEnvProbe: loginInteractiveShell
- WaitFor: updateContent if any updateContentCommand is configured, otherwise postCreate.
Init / Privileged / UpdateRemoteUserUID / ShutdownAction stay nil/"" when unset; the runtime treats nil / zero-value as "no override". This matches the spec — there is no positive default to apply.
Idempotent: safe to call multiple times.
type ResolvedFeature ¶
type ResolvedFeature struct {
Ref string
ResolvedRef string
Dir string
Metadata FeatureMetadata
Options map[string]any
SourceKind FeatureSourceKind
AlreadyInstalled bool
}
ResolvedFeature is a feature reference plus user-supplied options. It is progressively populated through the pipeline:
- After config.Resolve: Ref, Options (raw user input), SourceKind are populated. ResolvedRef, Dir, Metadata are empty; AlreadyInstalled is false.
- After feature.Order on the partial set: position in the slice reflects overrideFeatureInstallOrder if the user supplied it.
- After Engine.Up reads the base image's devcontainer.metadata label: AlreadyInstalled may flip true; in that case fetch is skipped and Dir/Metadata stay empty.
- After feature.Store.Fetch: Dir, ResolvedRef, Metadata populated.
- After feature.Order on the full set: position reflects the dependsOn / installsAfter DAG.
type ShutdownAction ¶
type ShutdownAction string
ShutdownAction selects how the container is treated when the workspace is closed.
const ( ShutdownNone ShutdownAction = "none" ShutdownStop ShutdownAction = "stop" ShutdownStopContainer ShutdownAction = "stopContainer" ShutdownStopCompose ShutdownAction = "stopCompose" )
type Source ¶
type Source interface {
Kind() SourceKind
// contains filtered or unexported methods
}
Source identifies where the container comes from. Implementations are sealed: ImageSource, BuildSource, ComposeSource.
type SourceKind ¶
type SourceKind string
const ( SourceImage SourceKind = "image" SourceBuild SourceKind = "build" SourceCompose SourceKind = "compose" )
type SubstitutionContext ¶
type SubstitutionContext struct {
LocalWorkspaceFolder string
ContainerWorkspaceFolder string
DevcontainerID string
LocalEnv map[string]string
ContainerEnv map[string]string
}
SubstitutionContext holds the values used to resolve placeholders in devcontainer.json strings. Fields left empty cause the corresponding variable to be preserved as a literal placeholder.
ContainerEnv is populated only after a container has been created and inspected; nil signals "host pass" and leaves ${containerEnv:*} references literal so the runtime layer can resolve them later.
type TmpfsOptions ¶
type UserEnvProbe ¶
type UserEnvProbe string
UserEnvProbe selects the shell strategy for capturing the user's interactive environment when computing remoteEnv.
const ( UserEnvProbeNone UserEnvProbe = "none" UserEnvProbeLoginShell UserEnvProbe = "loginShell" UserEnvProbeInteractiveShell UserEnvProbe = "interactiveShell" UserEnvProbeLoginInteractive UserEnvProbe = "loginInteractiveShell" )
type VolumeDriverConfig ¶
type VolumeOptions ¶
type VolumeOptions struct {
NoCopy bool
Labels map[string]string
DriverConfig *VolumeDriverConfig
}
type Warning ¶
type Warning struct {
Code WarningCode
Message string
Path string
Source string
}
Warning is a non-fatal diagnostic accumulated during parse, merge, or substitution. Path is a JSON pointer; Source identifies the contributing document (file path, image label reference, or feature id).
func ResolveString ¶
func ResolveString(s string, ctx SubstitutionContext) (string, []Warning)
ResolveString substitutes host-context variables in s.
Supported variables:
- ${localWorkspaceFolder}, ${localWorkspaceFolderBasename}
- ${containerWorkspaceFolder}, ${containerWorkspaceFolderBasename}
- ${devcontainerId}
- ${localEnv:VAR} and ${localEnv:VAR:default}
- ${env:VAR} (alias for localEnv)
Pass-through (left as literal): ${containerEnv:VAR} — resolved at runtime against the live container.
Undefined ${localEnv:VAR} (no default) substitutes to empty string and emits WarnUnresolvedLocalEnv, matching shell semantics. Unknown variable names are left as literals and emit WarnUnknownVariable.
type WarningCode ¶
type WarningCode string
WarningCode classifies a non-fatal diagnostic.
const ( WarnUnknownField WarningCode = "unknown_field" WarnDeprecatedKey WarningCode = "deprecated_key" WarnUnsupportedFeatureField WarningCode = "unsupported_feature_field" WarnUnresolvedLocalEnv WarningCode = "unresolved_local_env" WarnUnresolvedContainerEnv WarningCode = "unresolved_container_env" WarnUnknownVariable WarningCode = "unknown_variable" WarnDeepFeatureChain WarningCode = "deep_feature_chain" WarnUnknownFeatureOption WarningCode = "unknown_feature_option" WarnComposePortsIgnored WarningCode = "compose_ports_ignored" WarnUIDReconcileSkipped WarningCode = "uid_reconcile_skipped" )