config

package
v0.1.4 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: May 13, 2026 License: Apache-2.0 Imports: 11 Imported by: 0

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

Constants

This section is empty.

Variables

This section is empty.

Functions

func BoolOr

func BoolOr(p *bool, def bool) bool

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

func DevcontainerID(localWorkspaceFolder, configPath string) string

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

func SortedKeys[V any](m map[string]V) []string

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 BindOptions struct {
	Propagation    string
	CreateHostPath bool
}

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

type Command struct {
	Shell string
	Exec  []string
}

Command is a single executable invocation in either shell or exec form. Exactly one of Shell / Exec is populated.

func (Command) IsEmpty

func (c Command) IsEmpty() bool

type ComposeSource

type ComposeSource struct {
	Files       []string
	Service     string
	RunServices []string
}

func (*ComposeSource) Kind

func (*ComposeSource) Kind() SourceKind

type ConfigInvalidError

type ConfigInvalidError struct {
	Path    string
	Message string
}

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

type ConfigParseError struct {
	Path string
	Err  error
}

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

type LifecycleCommand struct {
	Single   *Command
	Parallel map[string]Command
}

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 MountType

type MountType string

MountType is the docker mount type.

const (
	MountBind   MountType = "bind"
	MountVolume MountType = "volume"
	MountTmpfs  MountType = "tmpfs"
)

type PortAttributes

type PortAttributes struct {
	Label            string
	Protocol         string
	OnAutoForward    string
	ElevateIfNeeded  bool
	RequireLocalPort bool
}

type PortSpec

type PortSpec struct {
	Container int
	Host      int
	Label     string
}

PortSpec describes a forwarded port. Host == 0 means "any free host port".

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 TmpfsOptions struct {
	SizeBytes int64
	Mode      uint32
}

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 VolumeDriverConfig struct {
	Name    string
	Options map[string]string
}

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"
)

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL