From c1224a46b22df6dc4d3c2089a043fb00b5cc2f01 Mon Sep 17 00:00:00 2001 From: Josh Spicer Date: Tue, 28 Feb 2023 16:18:09 +0000 Subject: [PATCH 01/12] goal of consolidate-merged-lifecycle-hook-schema --- proposals/consolidate-merged-lifecycle-hook-schema.md | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 proposals/consolidate-merged-lifecycle-hook-schema.md diff --git a/proposals/consolidate-merged-lifecycle-hook-schema.md b/proposals/consolidate-merged-lifecycle-hook-schema.md new file mode 100644 index 00000000..be4389ff --- /dev/null +++ b/proposals/consolidate-merged-lifecycle-hook-schema.md @@ -0,0 +1,4 @@ +# Goal + +Expand the lifecycle hook `devcontainer.json` schema to make it possible to represent a merged configuration within a `devcontainer.json`. This will allow a configuration comprised of lifecycle hooks comprised of [Feature contributed lifeycycle scripts](/workspaces/spec/proposals/features-contribute-lifecycle-scripts.md) or otherwise merged onto the image to not need a separate 'mergedConfiguration' schema to represent the config. + From 3a3e262de8ea444fadb1e58a89af8c7a2be6807b Mon Sep 17 00:00:00 2001 From: Josh Spicer Date: Tue, 28 Feb 2023 20:53:49 +0000 Subject: [PATCH 02/12] init consolidate-merged-lifecycle-hook-schema --- ...onsolidate-merged-lifecycle-hook-schema.md | 28 ++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/proposals/consolidate-merged-lifecycle-hook-schema.md b/proposals/consolidate-merged-lifecycle-hook-schema.md index be4389ff..5185be1f 100644 --- a/proposals/consolidate-merged-lifecycle-hook-schema.md +++ b/proposals/consolidate-merged-lifecycle-hook-schema.md @@ -1,4 +1,30 @@ # Goal -Expand the lifecycle hook `devcontainer.json` schema to make it possible to represent a merged configuration within a `devcontainer.json`. This will allow a configuration comprised of lifecycle hooks comprised of [Feature contributed lifeycycle scripts](/workspaces/spec/proposals/features-contribute-lifecycle-scripts.md) or otherwise merged onto the image to not need a separate 'mergedConfiguration' schema to represent the config. +Expand the lifecycle hook `devcontainer.json` schema to make it possible to represent a merged configuration within a `devcontainer.json`. This will allow a configuration comprised of lifecycle hooks comprised of [Feature contributed lifeycycle scripts](./features-contribute-lifecycle-scripts.md) or otherwise merged onto the image to not need a separate 'mergedConfiguration' schema to represent the config. +## Motivation + +As seen below, the current generated 'mergedConfiguration' returned by the `read-configuration` CLI command does not return a `devcontainer.json` as outlined in this repo's specification, but rather an array of hooks. [(source)](https://github.com/devcontainers/cli/pull/390#issuecomment-1430190326) + +![img-1](https://user-images.githubusercontent.com/23246594/218825633-cf037d97-db05-4d0d-9157-66287cd47073.png) + +## Proposal + +Update the schema of lifecycle hooks to mirror the merged configuration illustrated above. + +Propose to change the interface to: + +```typescript + +export type LifecycleCommand = string | string[] | { [key: string]: string | string[] }; +// export type LifecycleCommandEntry = LifecycleCommand | {lifeCycleCommand: LifecycleCommand; origin: string} + +interface DevContainerConfig { + // ...other devcontainer.json properties... + onCreateCommand?: LifecycleCommand | LifecycleCommand[]; + updateContentCommand?: LifecycleCommand | LifecycleCommand[]; + postCreateCommand?: LifecycleCommand | LifecycleCommand[]; + postStartCommand?: LifecycleCommand | LifecycleCommand[]; + postAttachCommand?: LifecycleCommand | LifecycleCommand[]; +} +``` \ No newline at end of file From 4ab251e3e10df7fb0aa74eacbe91911f58525858 Mon Sep 17 00:00:00 2001 From: Josh Spicer Date: Tue, 28 Feb 2023 21:49:46 +0000 Subject: [PATCH 03/12] proposal --- ...onsolidate-merged-lifecycle-hook-schema.md | 23 +++++++++++-------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/proposals/consolidate-merged-lifecycle-hook-schema.md b/proposals/consolidate-merged-lifecycle-hook-schema.md index 5185be1f..9fe3ca8f 100644 --- a/proposals/consolidate-merged-lifecycle-hook-schema.md +++ b/proposals/consolidate-merged-lifecycle-hook-schema.md @@ -1,6 +1,6 @@ # Goal -Expand the lifecycle hook `devcontainer.json` schema to make it possible to represent a merged configuration within a `devcontainer.json`. This will allow a configuration comprised of lifecycle hooks comprised of [Feature contributed lifeycycle scripts](./features-contribute-lifecycle-scripts.md) or otherwise merged onto the image to not need a separate 'mergedConfiguration' schema to represent the config. +Expand the lifecycle hook `devcontainer.json` schema to make it possible to represent a merged configuration within a `devcontainer.json`. This will allow a configuration comprised of lifecycle hooks comprised of [Feature contributed lifecycle scripts](./features-contribute-lifecycle-scripts.md) or otherwise merged onto the image to not need a separate 'mergedConfiguration' schema to represent the config. ## Motivation @@ -16,15 +16,18 @@ Propose to change the interface to: ```typescript -export type LifecycleCommand = string | string[] | { [key: string]: string | string[] }; -// export type LifecycleCommandEntry = LifecycleCommand | {lifeCycleCommand: LifecycleCommand; origin: string} +export type LifecycleCommand = string | string[] +export type LifecycleCommandParallel = { [key: string]: LifecycleCommand }; interface DevContainerConfig { // ...other devcontainer.json properties... - onCreateCommand?: LifecycleCommand | LifecycleCommand[]; - updateContentCommand?: LifecycleCommand | LifecycleCommand[]; - postCreateCommand?: LifecycleCommand | LifecycleCommand[]; - postStartCommand?: LifecycleCommand | LifecycleCommand[]; - postAttachCommand?: LifecycleCommand | LifecycleCommand[]; -} -``` \ No newline at end of file + onCreateCommand?: LifecycleCommand | LifecycleCommandParallel | LifecycleCommandParallel[] + updateContentCommand?: LifecycleCommand | LifecycleCommandParallel | LifecycleCommandParallel[] + postCreateCommand?: LifecycleCommand | LifecycleCommandParallel | LifecycleCommandParallel[] + postStartCommand?: LifecycleCommand | LifecycleCommandParallel | LifecycleCommandParallel[] + postAttachCommand?: LifecycleCommand | LifecycleCommandParallel | LifecycleCommandParallel[] +``` + +The new addition is the final unioned value `LifecycleCommandParallel[]`. Every existing lifecycle command and merged command can be represented within that type. + +Using this property, the 'mergedConfiguration' returned by `read-configuration` can directly return a `devcontainer.json` without needing non-spec properties (ie: plural `postCreateCommands`). \ No newline at end of file From 9e26125b2b6d3d5e4b62525ddc9e8747026f2ec7 Mon Sep 17 00:00:00 2001 From: Josh Spicer Date: Tue, 28 Feb 2023 14:17:27 -0800 Subject: [PATCH 04/12] Update consolidate-merged-lifecycle-hook-schema.md --- proposals/consolidate-merged-lifecycle-hook-schema.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/proposals/consolidate-merged-lifecycle-hook-schema.md b/proposals/consolidate-merged-lifecycle-hook-schema.md index 9fe3ca8f..840795d1 100644 --- a/proposals/consolidate-merged-lifecycle-hook-schema.md +++ b/proposals/consolidate-merged-lifecycle-hook-schema.md @@ -22,12 +22,13 @@ export type LifecycleCommandParallel = { [key: string]: LifecycleCommand }; interface DevContainerConfig { // ...other devcontainer.json properties... onCreateCommand?: LifecycleCommand | LifecycleCommandParallel | LifecycleCommandParallel[] - updateContentCommand?: LifecycleCommand | LifecycleCommandParallel | LifecycleCommandParallel[] + updateContentCommand?: LifecycleCommand | LifecycleCommandParallel | LifecycleCommandParallel[] postCreateCommand?: LifecycleCommand | LifecycleCommandParallel | LifecycleCommandParallel[] postStartCommand?: LifecycleCommand | LifecycleCommandParallel | LifecycleCommandParallel[] postAttachCommand?: LifecycleCommand | LifecycleCommandParallel | LifecycleCommandParallel[] +} ``` The new addition is the final unioned value `LifecycleCommandParallel[]`. Every existing lifecycle command and merged command can be represented within that type. -Using this property, the 'mergedConfiguration' returned by `read-configuration` can directly return a `devcontainer.json` without needing non-spec properties (ie: plural `postCreateCommands`). \ No newline at end of file +Using this property, the 'mergedConfiguration' returned by `read-configuration` can directly return a `devcontainer.json` without needing non-spec properties (ie: plural `postCreateCommands`). From 3a62b33a380730baa41cf5f57963ec36140a98c9 Mon Sep 17 00:00:00 2001 From: Josh Spicer Date: Thu, 9 Mar 2023 22:08:21 +0000 Subject: [PATCH 05/12] cover all properties --- ...onsolidate-merged-lifecycle-hook-schema.md | 109 ++++++++++++++++-- 1 file changed, 101 insertions(+), 8 deletions(-) diff --git a/proposals/consolidate-merged-lifecycle-hook-schema.md b/proposals/consolidate-merged-lifecycle-hook-schema.md index 840795d1..a4426be2 100644 --- a/proposals/consolidate-merged-lifecycle-hook-schema.md +++ b/proposals/consolidate-merged-lifecycle-hook-schema.md @@ -1,23 +1,28 @@ # Goal -Expand the lifecycle hook `devcontainer.json` schema to make it possible to represent a merged configuration within a `devcontainer.json`. This will allow a configuration comprised of lifecycle hooks comprised of [Feature contributed lifecycle scripts](./features-contribute-lifecycle-scripts.md) or otherwise merged onto the image to not need a separate 'mergedConfiguration' schema to represent the config. + +This proposal aims to raise the 'mergedConfiguration' into the specification, so that implementing tools can generate `devcontainer.json`, as well as consume, a spec-compliant 'mergedConfiguration' by parsing a user 'devcontainer.json'. Properties that can not or should not be represented as `devcontainer.json` properties are prepended with a `$`. This proposal standardizes how these properies should be outputted, so that supporting tooos can consume them consistently. ## Motivation -As seen below, the current generated 'mergedConfiguration' returned by the `read-configuration` CLI command does not return a `devcontainer.json` as outlined in this repo's specification, but rather an array of hooks. [(source)](https://github.com/devcontainers/cli/pull/390#issuecomment-1430190326) +The current generated 'mergedConfiguration' returned by the `read-configuration` CLI command does not return a `devcontainer.json` as outlined in this repo's specification. There are several inconsistencies. [(source)](https://github.com/devcontainers/cli/pull/390#issuecomment-1430190326) -![img-1](https://user-images.githubusercontent.com/23246594/218825633-cf037d97-db05-4d0d-9157-66287cd47073.png) +By expanding the lifecycle hook `devcontainer.json` schema as outlined below, it is possible to represent a merged configuration within a `devcontainer.json`. This, for example, will allow a configuration comprised of [Feature contributed lifecycle scripts](./features-contribute-lifecycle-scripts.md) to be represented as a `devcontainer.json` without needing to add non-spec properties (ie: plural `postCreateCommands`). ## Proposal -Update the schema of lifecycle hooks to mirror the merged configuration illustrated above. +### A. Update the devcontainer.json schema + +#### A1. Change the Lifecycle Script Interface -Propose to change the interface to: +Add a value (`LifecycleCommandParallel[]`) to the unioned definition of each lifecycle hook (except 'initializeCommand'). + +> NOTE: _ This was chosen as every existing lifecycle command (and merged version of each command) can be represented within that type._ ```typescript export type LifecycleCommand = string | string[] -export type LifecycleCommandParallel = { [key: string]: LifecycleCommand }; +export type LifecycleCommandParallel = { [key: string]: LifecycleCommand; $origin?: string }; interface DevContainerConfig { // ...other devcontainer.json properties... @@ -29,6 +34,94 @@ interface DevContainerConfig { } ``` -The new addition is the final unioned value `LifecycleCommandParallel[]`. Every existing lifecycle command and merged command can be represented within that type. +An optional parameter `$origin` is added to the `LifecycleCommandParallel` that supporting tools can use to indicate the source of the command. For example, this is useful for outputting in the creation log which Feature provided a certain lifecycle hook. The `$` notation is used to indicate this property is additional tooling metadata that should not be present in a user `devcontainer.json`. + +As an example, the following `devcontainer.json` snippet would be valid: + +```json +{ + "onCreateCommand": [ + { + "commandA": "echo 'Hello World!'", + "commandB": ["echo", "'Hello World!'"], + "$origin": "featureA" + }, + { + "commandA": "echo 'Hello World!'", + "commandB": ["echo", "'Hello World!'"], + "$origin": "devcontainer.json" + } + ] +} +``` + +### B. 'read-configuration' command to generate a merged configuration. + +The [reference implementation](https://github.com/devcontainers/cli) includes a 'read-configuration' command that can be used to resolve a fully merged configuration from a given `devcontainer.json`. + +Implementing tools should follow the [documented merging logic](https://containers.dev/implementors/spec/#merge-logic) and output a `devcontainer.json` that represents the resolved configuration. + +There are a couple special cases to the merging logic that should be noted: + +#### B1. The `customizations` property + +The merging of the `customizations` property is left to the implementing tool, therefore there is no defined merging pattern. + +A property `$customizations` should be added to the merged configuration as an array of customizations within each top-level customization object. Tooling that reads the mergedConfiguration can process this property as needed. + +In the below example, the `vscode` customization has two entries (contributed from two different sources), and the `foo` customization has two entries (contributed from two different sources). The merged `$customizations` property would be an array of two objects, one for each customization namespace. + +```json +"$customizations": { + // Customizations for the 'vscode' namespace + "vscode": [ + { + "settings": { + "settingA": "local", + "settingB": "/usr/bin/lldb" + }, + "extensions": [ + "GitHub.vscode-pull-request-github" + ] + }, + { + "settings": { + "settingA": "local", + "settingC": true + } + } + ], + // Customizations for the 'foo' namespace + "foo": [ + { + "bar": "baz", + "b": true + }, + { + "bar": "baz", + "a": true + } + ] +}, +... +``` + +### B2. The `entrypoint` property + +Dev Container Features are able to contribute an `entrypoint` property. This property is not available in the `devcontainer.json`. + +A property `$entrypoints` should be added to the merged configuration containing an array of entrypoint strings. Tooling that reads the mergedConfiguration can process this property as needed. + +In the below example, the `entrypoint` property is contributed from two different sources. The merged `$entrypoints` property would be an array of two strings, one for each entrypoint. + +```json +"$entrypoints": [ + "/usr/bin/entrypointA", + "/usr/bin/entrypointB" +], +``` + +## Summary + +With these additions, the 'mergedConfiguration' returned by `read-configuration` can directly return a `devcontainer.json` without needing non-spec properties (ie: plural `postCreateCommands`). The addition of `$origin` in the `LifecycleCommandParallel` type allows for tooling to indicate the source of a lifecycle hook. The addition of `$customizations` and `$entrypoints` allows for tooling to indicate the source of a customization or entrypoint without needing to add non-schema properties to the `devcontainer.json`. -Using this property, the 'mergedConfiguration' returned by `read-configuration` can directly return a `devcontainer.json` without needing non-spec properties (ie: plural `postCreateCommands`). From a778863e599910f9ced57aa112f21bd7a340f56f Mon Sep 17 00:00:00 2001 From: Josh Spicer Date: Thu, 9 Mar 2023 22:09:17 +0000 Subject: [PATCH 06/12] indent; --- ...onsolidate-merged-lifecycle-hook-schema.md | 42 +++++++++---------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/proposals/consolidate-merged-lifecycle-hook-schema.md b/proposals/consolidate-merged-lifecycle-hook-schema.md index a4426be2..c4396628 100644 --- a/proposals/consolidate-merged-lifecycle-hook-schema.md +++ b/proposals/consolidate-merged-lifecycle-hook-schema.md @@ -75,32 +75,32 @@ In the below example, the `vscode` customization has two entries (contributed fr "$customizations": { // Customizations for the 'vscode' namespace "vscode": [ - { - "settings": { - "settingA": "local", - "settingB": "/usr/bin/lldb" + { + "settings": { + "settingA": "local", + "settingB": "/usr/bin/lldb" + }, + "extensions": [ + "GitHub.vscode-pull-request-github" + ] }, - "extensions": [ - "GitHub.vscode-pull-request-github" - ] - }, - { - "settings": { - "settingA": "local", - "settingC": true + { + "settings": { + "settingA": "local", + "settingC": true + } } - } ], // Customizations for the 'foo' namespace "foo": [ - { - "bar": "baz", - "b": true - }, - { - "bar": "baz", - "a": true - } + { + "bar": "baz", + "b": true + }, + { + "bar": "baz", + "a": true + } ] }, ... From aa048eb0755952f447cab62fd99189eaf91ada38 Mon Sep 17 00:00:00 2001 From: Josh Spicer Date: Thu, 9 Mar 2023 22:19:02 +0000 Subject: [PATCH 07/12] proofreading --- proposals/consolidate-merged-lifecycle-hook-schema.md | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/proposals/consolidate-merged-lifecycle-hook-schema.md b/proposals/consolidate-merged-lifecycle-hook-schema.md index c4396628..bb078a9c 100644 --- a/proposals/consolidate-merged-lifecycle-hook-schema.md +++ b/proposals/consolidate-merged-lifecycle-hook-schema.md @@ -1,19 +1,18 @@ # Goal +This proposal aims to raise a concept of the 'mergedConfiguration' into the specification, so that implementing tools can generate, as well as consume, a 'merged' `devcontainer.json` file that is the computed merge after parsing a user 'devcontainer.json'. [The merging logic is already documented](https://containers.dev/implementors/spec/#merge-logic). This proposal focuses on standarding an output JSON format to represent this merge. -This proposal aims to raise the 'mergedConfiguration' into the specification, so that implementing tools can generate `devcontainer.json`, as well as consume, a spec-compliant 'mergedConfiguration' by parsing a user 'devcontainer.json'. Properties that can not or should not be represented as `devcontainer.json` properties are prepended with a `$`. This proposal standardizes how these properies should be outputted, so that supporting tooos can consume them consistently. +Properties that can not or should not be represented as `devcontainer.json` properties are prepended with a `$`. This proposal standardizes how these properies should be processed and outputted to file, so that implementing tools can write/consume them consistently. ## Motivation -The current generated 'mergedConfiguration' returned by the `read-configuration` CLI command does not return a `devcontainer.json` as outlined in this repo's specification. There are several inconsistencies. [(source)](https://github.com/devcontainers/cli/pull/390#issuecomment-1430190326) - -By expanding the lifecycle hook `devcontainer.json` schema as outlined below, it is possible to represent a merged configuration within a `devcontainer.json`. This, for example, will allow a configuration comprised of [Feature contributed lifecycle scripts](./features-contribute-lifecycle-scripts.md) to be represented as a `devcontainer.json` without needing to add non-spec properties (ie: plural `postCreateCommands`). +The current generated 'mergedConfiguration' returned by the `read-configuration` CLI command does not return a `devcontainer.json`. [(visualization)](https://github.com/devcontainers/cli/pull/390#issuecomment-1430190326). By standardizing the output format, implementing tools can generate a portable, merged `devcontainer.json` that can be consumed by other tools. ## Proposal ### A. Update the devcontainer.json schema -#### A1. Change the Lifecycle Script Interface +#### A1. Change the lifecycle script interface Add a value (`LifecycleCommandParallel[]`) to the unioned definition of each lifecycle hook (except 'initializeCommand'). @@ -55,7 +54,7 @@ As an example, the following `devcontainer.json` snippet would be valid: } ``` -### B. 'read-configuration' command to generate a merged configuration. +### B. Generating a merged configuration The [reference implementation](https://github.com/devcontainers/cli) includes a 'read-configuration' command that can be used to resolve a fully merged configuration from a given `devcontainer.json`. From 465381dd21e3072b5d373115f6f3595207a60d9f Mon Sep 17 00:00:00 2001 From: Josh Spicer Date: Thu, 16 Mar 2023 21:58:48 +0000 Subject: [PATCH 08/12] customizations array variant --- ...onsolidate-merged-lifecycle-hook-schema.md | 61 ++++++++++++------- 1 file changed, 38 insertions(+), 23 deletions(-) diff --git a/proposals/consolidate-merged-lifecycle-hook-schema.md b/proposals/consolidate-merged-lifecycle-hook-schema.md index bb078a9c..cc1b2434 100644 --- a/proposals/consolidate-merged-lifecycle-hook-schema.md +++ b/proposals/consolidate-merged-lifecycle-hook-schema.md @@ -12,7 +12,7 @@ The current generated 'mergedConfiguration' returned by the `read-configuration` ### A. Update the devcontainer.json schema -#### A1. Change the lifecycle script interface +#### A1. Extends lifecycle script properties Add a value (`LifecycleCommandParallel[]`) to the unioned definition of each lifecycle hook (except 'initializeCommand'). @@ -54,58 +54,73 @@ As an example, the following `devcontainer.json` snippet would be valid: } ``` -### B. Generating a merged configuration - -The [reference implementation](https://github.com/devcontainers/cli) includes a 'read-configuration' command that can be used to resolve a fully merged configuration from a given `devcontainer.json`. +#### A2. Extend `customizations` property -Implementing tools should follow the [documented merging logic](https://containers.dev/implementors/spec/#merge-logic) and output a `devcontainer.json` that represents the resolved configuration. +The merging of the `customizations` property is left to the implementing tool, therefore there is no defined merging pattern. -There are a couple special cases to the merging logic that should be noted: +Extend the interface of to include another unioned type: -#### B1. The `customizations` property +```typescript +interface DevContainerConfig { + // ...other devcontainer.json properties... + customizations?: Record | Record +} +``` -The merging of the `customizations` property is left to the implementing tool, therefore there is no defined merging pattern. +The addition of union type `Record` (and explicitly typing the existing case to an object) allows for various tools to contribute customizations to the same namespace. For example, a Feature may contribute a `settings` object, and a user (via `devcontainer.json`) may contribute a `settings` object. The merged `customizations` property would be an array of two objects. This allows for the merging algorithm to be defined by the implementing tool. -A property `$customizations` should be added to the merged configuration as an array of customizations within each top-level customization object. Tooling that reads the mergedConfiguration can process this property as needed. +Similar to above, the `$origin` property may be added by tools to indicate the source of the customization. -In the below example, the `vscode` customization has two entries (contributed from two different sources), and the `foo` customization has two entries (contributed from two different sources). The merged `$customizations` property would be an array of two objects, one for each customization namespace. +In the below example, the `vscode` customization has two entries (contributed from two different sources), and the `foo` customization has two entries (contributed from two different sources). The merged `customizations` property would be an array of two objects for each customization namespace. ```json -"$customizations": { +"customizations": { // Customizations for the 'vscode' namespace "vscode": [ { "settings": { - "settingA": "local", - "settingB": "/usr/bin/lldb" + "settingA": "local", + "settingB": "/usr/bin/lldb" }, "extensions": [ - "GitHub.vscode-pull-request-github" - ] + "GitHub.vscode-pull-request-github" + ], + "$origin": "featureA" }, { "settings": { - "settingA": "local", - "settingC": true - } + "settingA": "local", + "settingC": true + }, + "$origin": "devcontainer.json" } ], // Customizations for the 'foo' namespace "foo": [ { "bar": "baz", - "b": true + "b": true, + "$origin": "featureA" }, { "bar": "baz", - "a": true + "a": true, + "$origin": "devcontainer.json" } ] -}, +} ... ``` -### B2. The `entrypoint` property +### B. Generating a merged configuration + +The [reference implementation](https://github.com/devcontainers/cli) includes a 'read-configuration' command that can be used to resolve a fully merged configuration from a given `devcontainer.json`. + +Implementing tools should follow the [documented merging logic](https://containers.dev/implementors/spec/#merge-logic) and output a `devcontainer.json` that represents the resolved configuration. + +Special cases are detailed below. + +### B1. The `entrypoint` property Dev Container Features are able to contribute an `entrypoint` property. This property is not available in the `devcontainer.json`. @@ -122,5 +137,5 @@ In the below example, the `entrypoint` property is contributed from two differen ## Summary -With these additions, the 'mergedConfiguration' returned by `read-configuration` can directly return a `devcontainer.json` without needing non-spec properties (ie: plural `postCreateCommands`). The addition of `$origin` in the `LifecycleCommandParallel` type allows for tooling to indicate the source of a lifecycle hook. The addition of `$customizations` and `$entrypoints` allows for tooling to indicate the source of a customization or entrypoint without needing to add non-schema properties to the `devcontainer.json`. +With these additions, the 'mergedConfiguration' returned by `read-configuration` can directly return a `devcontainer.json` without needing non-spec properties (ie: plural `postCreateCommands`). The addition of `$origin` in the `LifecycleCommandParallel` and `customizations` array variant types allows for tooling to indicate the source of provided functionality. The addition of `$entrypoints` allows for tooling to output metadata on the contributed entrypoint(s) without needing to add non-schema properties to the `devcontainer.json`. From 56b107ceaa8c4a6edf3f73badd325fafab30525d Mon Sep 17 00:00:00 2001 From: Josh Spicer Date: Thu, 16 Mar 2023 14:59:50 -0700 Subject: [PATCH 09/12] Apply suggestions from code review Co-authored-by: Brigit Murtaugh --- proposals/consolidate-merged-lifecycle-hook-schema.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/proposals/consolidate-merged-lifecycle-hook-schema.md b/proposals/consolidate-merged-lifecycle-hook-schema.md index cc1b2434..5234fbbe 100644 --- a/proposals/consolidate-merged-lifecycle-hook-schema.md +++ b/proposals/consolidate-merged-lifecycle-hook-schema.md @@ -1,8 +1,8 @@ # Goal -This proposal aims to raise a concept of the 'mergedConfiguration' into the specification, so that implementing tools can generate, as well as consume, a 'merged' `devcontainer.json` file that is the computed merge after parsing a user 'devcontainer.json'. [The merging logic is already documented](https://containers.dev/implementors/spec/#merge-logic). This proposal focuses on standarding an output JSON format to represent this merge. +This proposal aims to raise a concept of the 'mergedConfiguration' into the specification, so that implementing tools can generate, as well as consume, a 'merged' `devcontainer.json` file that is the computed merge after parsing a user 'devcontainer.json'. [The merging logic is already documented](https://containers.dev/implementors/spec/#merge-logic). This proposal focuses on standardizing an output JSON format to represent this merge. -Properties that can not or should not be represented as `devcontainer.json` properties are prepended with a `$`. This proposal standardizes how these properies should be processed and outputted to file, so that implementing tools can write/consume them consistently. +Properties that can not or should not be represented as `devcontainer.json` properties are prepended with a `$`. This proposal standardizes how these properties should be processed and outputted to file, so that implementing tools can write/consume them consistently. ## Motivation @@ -124,7 +124,7 @@ Special cases are detailed below. Dev Container Features are able to contribute an `entrypoint` property. This property is not available in the `devcontainer.json`. -A property `$entrypoints` should be added to the merged configuration containing an array of entrypoint strings. Tooling that reads the mergedConfiguration can process this property as needed. +A property `$entrypoints` should be added to the merged configuration containing an array of entrypoint strings. Tooling that reads the 'mergedConfiguration' can process this property as needed. In the below example, the `entrypoint` property is contributed from two different sources. The merged `$entrypoints` property would be an array of two strings, one for each entrypoint. From 1dc72c9758249c1b14a4d1d2b17539230b8467ca Mon Sep 17 00:00:00 2001 From: Josh Spicer Date: Thu, 16 Mar 2023 22:04:00 +0000 Subject: [PATCH 10/12] italics --- proposals/consolidate-merged-lifecycle-hook-schema.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/proposals/consolidate-merged-lifecycle-hook-schema.md b/proposals/consolidate-merged-lifecycle-hook-schema.md index 5234fbbe..d3b4a261 100644 --- a/proposals/consolidate-merged-lifecycle-hook-schema.md +++ b/proposals/consolidate-merged-lifecycle-hook-schema.md @@ -16,7 +16,7 @@ The current generated 'mergedConfiguration' returned by the `read-configuration` Add a value (`LifecycleCommandParallel[]`) to the unioned definition of each lifecycle hook (except 'initializeCommand'). -> NOTE: _ This was chosen as every existing lifecycle command (and merged version of each command) can be represented within that type._ +> NOTE: _This was chosen as every existing lifecycle command (and merged version of each command) can be represented within that type._ ```typescript From 452b918486e93aa8447999b075aeaaee38a48c58 Mon Sep 17 00:00:00 2001 From: Josh Spicer Date: Tue, 27 Jun 2023 23:22:04 +0000 Subject: [PATCH 11/12] code review edits --- ...onsolidate-merged-lifecycle-hook-schema.md | 47 ++++++++++--------- 1 file changed, 24 insertions(+), 23 deletions(-) diff --git a/proposals/consolidate-merged-lifecycle-hook-schema.md b/proposals/consolidate-merged-lifecycle-hook-schema.md index d3b4a261..4df59286 100644 --- a/proposals/consolidate-merged-lifecycle-hook-schema.md +++ b/proposals/consolidate-merged-lifecycle-hook-schema.md @@ -14,16 +14,17 @@ The current generated 'mergedConfiguration' returned by the `read-configuration` #### A1. Extends lifecycle script properties -Add a value (`LifecycleCommandParallel[]`) to the unioned definition of each lifecycle hook (except 'initializeCommand'). - -> NOTE: _This was chosen as every existing lifecycle command (and merged version of each command) can be represented within that type._ +Add a value (`LifecycleCommandParallel[]`) to the unioned definition of each lifecycle hook (except 'initializeCommand'). This allows for a syntax that captures the existing allowed types for a lifecycle hook and additionally provides a pattern for attaching additional metadata to a merged configuration. ```typescript +LifecycleCommand = string | string[] +LifecycleCommandParallel = { [key: string]: LifecycleCommand; $origin?: string }; +``` -export type LifecycleCommand = string | string[] -export type LifecycleCommandParallel = { [key: string]: LifecycleCommand; $origin?: string }; +In a `devcontainer.json`, each of the following lifecycle hooks shown below can now be represented by one of these three types. -interface DevContainerConfig { +```typescript +{ // ...other devcontainer.json properties... onCreateCommand?: LifecycleCommand | LifecycleCommandParallel | LifecycleCommandParallel[] updateContentCommand?: LifecycleCommand | LifecycleCommandParallel | LifecycleCommandParallel[] @@ -35,6 +36,8 @@ interface DevContainerConfig { An optional parameter `$origin` is added to the `LifecycleCommandParallel` that supporting tools can use to indicate the source of the command. For example, this is useful for outputting in the creation log which Feature provided a certain lifecycle hook. The `$` notation is used to indicate this property is additional tooling metadata that should not be present in a user `devcontainer.json`. +> Per [the 'Allow Dev Container Features to contribute lifecycle scripts' specification](/proposals/features-contribute-lifecycle-scripts.md), each `LifecycleCommandParallel` element in the list variant will execute sequentially, in list order. + As an example, the following `devcontainer.json` snippet would be valid: ```json @@ -43,7 +46,7 @@ As an example, the following `devcontainer.json` snippet would be valid: { "commandA": "echo 'Hello World!'", "commandB": ["echo", "'Hello World!'"], - "$origin": "featureA" + "$origin": "ghcr.io/devcontainers/featureA@sha256:1234567890" }, { "commandA": "echo 'Hello World!'", @@ -58,16 +61,9 @@ As an example, the following `devcontainer.json` snippet would be valid: The merging of the `customizations` property is left to the implementing tool, therefore there is no defined merging pattern. -Extend the interface of to include another unioned type: +Extend the `devcontainer.json` such that each top-level `customization` namespace is either a map or an array of maps. The former represents the existing syntax and the latter allows for a namespace's merging algorithm to be defined by the implementing tool. -```typescript -interface DevContainerConfig { - // ...other devcontainer.json properties... - customizations?: Record | Record -} -``` - -The addition of union type `Record` (and explicitly typing the existing case to an object) allows for various tools to contribute customizations to the same namespace. For example, a Feature may contribute a `settings` object, and a user (via `devcontainer.json`) may contribute a `settings` object. The merged `customizations` property would be an array of two objects. This allows for the merging algorithm to be defined by the implementing tool. +The addition of the 'array of maps' syntax allows for various tools to contribute customizations to the same namespace. For example, a Feature may contribute a `vscode` object, and a user (via `devcontainer.json`) may contribute a `vscode` object. The merged `customizations` property would be an array of two objects. This allows for the merging algorithm to be defined by the implementing tool. Similar to above, the `$origin` property may be added by tools to indicate the source of the customization. @@ -85,7 +81,7 @@ In the below example, the `vscode` customization has two entries (contributed fr "extensions": [ "GitHub.vscode-pull-request-github" ], - "$origin": "featureA" + "$origin": "ghcr.io/devcontainers/featureA@sha256:1234567890" }, { "settings": { @@ -100,7 +96,7 @@ In the below example, the `vscode` customization has two entries (contributed fr { "bar": "baz", "b": true, - "$origin": "featureA" + "$origin": "ghcr.io/devcontainers/featureA@sha256:1234567890" }, { "bar": "baz", @@ -116,8 +112,6 @@ In the below example, the `vscode` customization has two entries (contributed fr The [reference implementation](https://github.com/devcontainers/cli) includes a 'read-configuration' command that can be used to resolve a fully merged configuration from a given `devcontainer.json`. -Implementing tools should follow the [documented merging logic](https://containers.dev/implementors/spec/#merge-logic) and output a `devcontainer.json` that represents the resolved configuration. - Special cases are detailed below. ### B1. The `entrypoint` property @@ -126,12 +120,18 @@ Dev Container Features are able to contribute an `entrypoint` property. This pro A property `$entrypoints` should be added to the merged configuration containing an array of entrypoint strings. Tooling that reads the 'mergedConfiguration' can process this property as needed. -In the below example, the `entrypoint` property is contributed from two different sources. The merged `$entrypoints` property would be an array of two strings, one for each entrypoint. +In the below example, the `entrypoint` property is contributed from two different sources. The merged `$entrypoints` property would be an array of two strings, one for each entrypoint. An optional `$origin` property may be added by tools to indicate the source of the entrypoint. ```json "$entrypoints": [ - "/usr/bin/entrypointA", - "/usr/bin/entrypointB" + { + "entrypoint": "/usr/bin/entrypointA", + "$origin": "ghcr.io/devcontainers/featureA@sha256:1234567890" + }, + { + "entrypoint": "/usr/bin/entrypointB", + "$origin": "ghcr.io/devcontainers/featureB@sha256:1234567890" + }, ], ``` @@ -139,3 +139,4 @@ In the below example, the `entrypoint` property is contributed from two differen With these additions, the 'mergedConfiguration' returned by `read-configuration` can directly return a `devcontainer.json` without needing non-spec properties (ie: plural `postCreateCommands`). The addition of `$origin` in the `LifecycleCommandParallel` and `customizations` array variant types allows for tooling to indicate the source of provided functionality. The addition of `$entrypoints` allows for tooling to output metadata on the contributed entrypoint(s) without needing to add non-schema properties to the `devcontainer.json`. + \ No newline at end of file From b19219f5c3e147d002231781f7d1b7ad5184923f Mon Sep 17 00:00:00 2001 From: Josh Spicer Date: Wed, 28 Jun 2023 23:23:43 +0000 Subject: [PATCH 12/12] clarify $entrypoints --- proposals/consolidate-merged-lifecycle-hook-schema.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/proposals/consolidate-merged-lifecycle-hook-schema.md b/proposals/consolidate-merged-lifecycle-hook-schema.md index 4df59286..303dca73 100644 --- a/proposals/consolidate-merged-lifecycle-hook-schema.md +++ b/proposals/consolidate-merged-lifecycle-hook-schema.md @@ -118,9 +118,9 @@ Special cases are detailed below. Dev Container Features are able to contribute an `entrypoint` property. This property is not available in the `devcontainer.json`. -A property `$entrypoints` should be added to the merged configuration containing an array of entrypoint strings. Tooling that reads the 'mergedConfiguration' can process this property as needed. +A property `$entrypoints` should be added to the merged configuration containing an array of entrypoint objects. Each object contains a required `entrypoint` string property and an optional `$origin` meta property. Tooling that reads the 'mergedConfiguration' can process this property as needed. -In the below example, the `entrypoint` property is contributed from two different sources. The merged `$entrypoints` property would be an array of two strings, one for each entrypoint. An optional `$origin` property may be added by tools to indicate the source of the entrypoint. +In the below example, the `entrypoint` property is contributed from two different sources. The merged `$entrypoints` property represents the set of active entrypoints. The optional `$origin` property may be added by tools to indicate the source of the entrypoint. ```json "$entrypoints": [