Skip to content

Commit

Permalink
Add support of devcontainer.user.json file
Browse files Browse the repository at this point in the history
  • Loading branch information
aacebedo committed Oct 28, 2024
1 parent 4513077 commit d2d6d9a
Show file tree
Hide file tree
Showing 22 changed files with 1,480 additions and 31 deletions.
14 changes: 14 additions & 0 deletions cmd/up.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,19 @@ func NewUpCmd(f *flags.GlobalFlags) *cobra.Command {
upCmd := &cobra.Command{
Use: "up",
Short: "Starts a new workspace",
PreRunE: func(_ *cobra.Command, args []string) error {
absExtraDevContainerPaths := []string{}
for _, extraPath := range cmd.ExtraDevContainerPaths {
absExtraPath, err := filepath.Abs(extraPath)
if err != nil {
return err
}

absExtraDevContainerPaths = append(absExtraDevContainerPaths, absExtraPath)
}
cmd.ExtraDevContainerPaths = absExtraDevContainerPaths
return nil
},
RunE: func(_ *cobra.Command, args []string) error {
devPodConfig, err := config.LoadConfig(cmd.Context, cmd.Provider)
if err != nil {
Expand Down Expand Up @@ -158,6 +171,7 @@ func NewUpCmd(f *flags.GlobalFlags) *cobra.Command {
upCmd.Flags().StringVar(&cmd.DevContainerImage, "devcontainer-image", "", "The container image to use, this will override the devcontainer.json value in the project")
upCmd.Flags().StringVar(&cmd.DevContainerPath, "devcontainer-path", "", "The path to the devcontainer.json relative to the project")
upCmd.Flags().StringVar(&cmd.DevContainerSource, "devcontainer-source", "", "External devcontainer.json source")
upCmd.Flags().StringArrayVar(&cmd.ExtraDevContainerPaths, "extra-devcontainer-path", []string{}, "The path to additional devcontainer.json files to override original devcontainer.json")
upCmd.Flags().StringVar(&cmd.EnvironmentTemplate, "environment-template", "", "Environment template to use")
_ = upCmd.Flags().MarkHidden("environment-template")
upCmd.Flags().StringArrayVar(&cmd.ProviderOptions, "provider-option", []string{}, "Provider option in the form KEY=VALUE")
Expand Down
27 changes: 16 additions & 11 deletions docs/pages/developing-in-workspaces/create-a-workspace.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,20 @@ You can create a workspace either from the DevPod CLI or through the DevPod desk
Upon successful creation, DevPod will make the development container available through the ssh host `WORKSPACE_NAME.devpod`. Alternatively, DevPod can automatically open the workspace in a locally installed IDE, such as VS Code or Intellij.

:::info
A workspace is defined through a `devcontainer.json`. If DevPod can't find one, it will automatically try to guess the programming language of your project and provide a fitting template.
A workspace is defined through a `devcontainer.json`. If DevPod can’t find one, it will automatically try to guess the programming language of your project and provide a fitting template.
:::

:::info
It is possible to override a `devcontainer.json` with specific user settings such as mounts by creating a file named `devcontainer.user.json` in the same directory as the `devcontainer.json` of the workspace.
This can be useful when customization of a versioned devcontainer is needed.
:::

### Via DevPod Desktop Application

Navigate to the 'Workspaces' view and click on the 'Create' button in the title. Enter the git repository you want to work on or select a local folder.
Navigate to the Workspaces view and click on the Create button in the title. Enter the git repository you want to work on or select a local folder.

:::info Add Provider
If you haven't configured a provider yet, DevPod will automatically open the provider modal for you. You can later add providers in the same way by navigating to 'Providers' > 'Add'
If you havent configured a provider yet, DevPod will automatically open the provider modal for you. You can later add providers in the same way by navigating to Providers > Add
:::

You can also configure one of the additional settings:
Expand All @@ -34,19 +39,19 @@ Under the hood, the Desktop Application will call the CLI command `devpod up REP
:::

:::info Note
You can set the location of your devpod home by passing the `--devpod-home={home_path}` flag,
You can set the location of your devpod home by passing the `--devpod-home={home_path}` flag,
or by setting the env var `DEVPOD_HOME` to your desired home directory.

This can be useful if you are having trouble with a workspace trying to mount to a windows location when it should be mounting to a path inside the WSL VM.

For example: setting `devpod-home=/mnt/c/Users/MyUser/` will result in a workspace path of something like `/mnt/c/Users/MyUser/.devpod/contexts/default/workspaces/...`
For example: setting `devpod-home=/mnt/c/Users/MyUser/` will result in a workspace path of something like `/mnt/c/Users/MyUser/.devpod/contexts/default/workspaces/`
:::

### Via DevPod CLI

Make sure to [install the DevPod CLI locally](../getting-started/install.mdx#optional-install-devpod-cli) and select a provider you would like to host the workspace on (such as local docker) via:
```
# Add a provider if you haven't already
# Add a provider if you havent already
devpod provider add docker
```

Expand Down Expand Up @@ -99,15 +104,15 @@ devpod up ghcr.io/my-org/my-repo:latest
DevPod will create the following `.devcontainer.json`:
```
{
"image": "ghcr.io/my-org/my-repo:latest"
image”: “ghcr.io/my-org/my-repo:latest
}
```

#### Existing local container

If you have a local container running, you can create a workspace from it by running:
```
devpod up my-workspace --source container:$CONTAINER_ID
devpod up my-workspace --source container:$CONTAINER_ID
```

This only works with the `docker` provider.
Expand All @@ -124,7 +129,7 @@ When recreating a workspace, changes only to the project path or mounted volumes

### Via DevPod Desktop Application

Navigate to the 'Workspaces' view and press on the 'More Options' button on the workspace you want to recreate. Then press 'Rebuild' and confirm to rebuild the workspace.
Navigate to the Workspaces view and press on the More Options button on the workspace you want to recreate. Then press Rebuild and confirm to rebuild the workspace.

### Via DevPod CLI

Expand All @@ -141,11 +146,11 @@ Some scenarios require pulling in the latest changes from a git repository or re

### Via DevPod Desktop Application

Navigate to the 'Workspaces' view and press on the 'More Options' button on the workspace you want to reset. Then press 'Reset' and confirm.
Navigate to the Workspaces view and press on the More Options button on the workspace you want to reset. Then press Reset and confirm.

### Via DevPod CLI

Run the following command to reset an existing workspace:
```
devpod up my-workspace --reset
```
```
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ require (

require (
cloud.google.com/go/compute/metadata v0.5.0 // indirect
dario.cat/mergo v1.0.1 // indirect
github.com/AlecAivazis/survey/v2 v2.3.7 // indirect
github.com/Azure/azure-sdk-for-go v68.0.0+incompatible // indirect
github.com/Azure/go-autorest v14.2.0+incompatible // indirect
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIA
cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=
cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s=
dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
github.com/AlecAivazis/survey/v2 v2.3.7 h1:6I/u8FvytdGsgonrYsVn2t8t4QiRnh6QSTqkkhIiSjQ=
github.com/AlecAivazis/survey/v2 v2.3.7/go.mod h1:xUTIdE4KCOIjsBAE1JYsUPoCqYdZ1reCfTwbto0Fduo=
Expand Down
30 changes: 30 additions & 0 deletions pkg/devcontainer/compose.go
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,21 @@ func (r *runner) runDockerCompose(
return nil, errors.Wrap(err, "get image metadata from container")
}

userConfig, err := config.ParseDevContainerUserJSON(parsedConfig.Config)
if err != nil {
return nil, err
} else if userConfig != nil {
config.AddConfigToImageMetadata(userConfig, imageMetadataConfig)
}

for _, v := range options.ExtraDevContainerPaths {
extraConfig, err := config.ParseDevContainerJSONFile(v)
if err != nil {
return nil, err
}
config.AddConfigToImageMetadata(extraConfig, imageMetadataConfig)
}

mergedConfig, err := config.MergeConfiguration(parsedConfig.Config, imageMetadataConfig.Config)
if err != nil {
return nil, errors.Wrap(err, "merge config")
Expand Down Expand Up @@ -332,6 +347,21 @@ func (r *runner) startContainer(
return nil, errors.Wrap(err, "inspect image")
}

userConfig, err := config.ParseDevContainerUserJSON(parsedConfig.Config)
if err != nil {
return nil, err
} else if userConfig != nil {
config.AddConfigToImageMetadata(userConfig, imageMetadata)
}

for _, v := range options.ExtraDevContainerPaths {
extraConfig, err := config.ParseDevContainerJSONFile(v)
if err != nil {
return nil, err
}
config.AddConfigToImageMetadata(extraConfig, imageMetadata)
}

mergedConfig, err := config.MergeConfiguration(parsedConfig.Config, imageMetadata.Config)
if err != nil {
return nil, errors.Wrap(err, "merge configuration")
Expand Down
8 changes: 8 additions & 0 deletions pkg/devcontainer/config/metadata.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,11 @@ type ImageMetadata struct {
DevContainerActions `json:",inline"`
NonComposeBase `json:",inline"`
}

func AddConfigToImageMetadata(config *DevContainerConfig, imageMetadataConfig *ImageMetadataConfig) {
userMetadata := &ImageMetadata{}
userMetadata.DevContainerConfigBase = config.DevContainerConfigBase
userMetadata.DevContainerActions = config.DevContainerActions
userMetadata.NonComposeBase = config.NonComposeBase
imageMetadataConfig.Config = append(imageMetadataConfig.Config, userMetadata)
}
61 changes: 41 additions & 20 deletions pkg/devcontainer/config/parse.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,46 @@ func SaveDevContainerJSON(config *DevContainerConfig) error {
return nil
}

func ParseDevContainerJSONFile(jsonFilePath string) (*DevContainerConfig, error) {
var err error
path, err := filepath.Abs(jsonFilePath)
if err != nil {
return nil, errors.Wrap(err, "make path absolute")
}

bytes, err := os.ReadFile(path)
if err != nil {
return nil, err
}

devContainer := &DevContainerConfig{}
err = json.Unmarshal(jsonc.ToJSON(bytes), devContainer)
if err != nil {
return nil, err
}
devContainer.Origin = path
return replaceLegacy(devContainer)
}


func ParseDevContainerUserJSON(config *DevContainerConfig) (*DevContainerConfig, error) {
filename := filepath.Base(config.Origin)
filename = strings.TrimSuffix(filename, filepath.Ext(filename))

devContainerUserUserFilename := fmt.Sprintf("%s.user.json", filename)
devContainerUserUserFilePath := filepath.Join(filepath.Dir(config.Origin), devContainerUserUserFilename)

_, err = os.Stat(devContainerUserUserFilePath)
if err == nil {
userConfig, err := ParseDevContainerJSONFile(devContainerUserUserFilePath)
if err != nil {
return nil, err
}
return userConfig, nil
}
return nil, nil
}

func ParseDevContainerJSON(folder, relativePath string) (*DevContainerConfig, error) {
path := ""
if relativePath != "" {
Expand All @@ -91,26 +131,7 @@ func ParseDevContainerJSON(folder, relativePath string) (*DevContainerConfig, er
}
}
}

var err error
path, err = filepath.Abs(path)
if err != nil {
return nil, errors.Wrap(err, "make path absolute")
}

bytes, err := os.ReadFile(path)
if err != nil {
return nil, err
}

devContainer := &DevContainerConfig{}
err = json.Unmarshal(jsonc.ToJSON(bytes), devContainer)
if err != nil {
return nil, err
}

devContainer.Origin = path
return replaceLegacy(devContainer)
return ParseDevContainerJSONFile(path)
}

func replaceLegacy(config *DevContainerConfig) (*DevContainerConfig, error) {
Expand Down
30 changes: 30 additions & 0 deletions pkg/devcontainer/single.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,21 @@ func (r *runner) runSingleContainer(
return nil, err
}

userConfig, err := config.ParseDevContainerUserJSON(parsedConfig.Config)
if err != nil {
return nil, err
} else if userConfig != nil {
config.AddConfigToImageMetadata(userConfig, imageMetadataConfig)
}

for _, v := range options.ExtraDevContainerPaths {
extraConfig, err := config.ParseDevContainerJSONFile(v)
if err != nil {
return nil, err
}
config.AddConfigToImageMetadata(extraConfig, imageMetadataConfig)
}

mergedConfig, err = config.MergeConfiguration(parsedConfig.Config, imageMetadataConfig.Config)
if err != nil {
return nil, errors.Wrap(err, "merge config")
Expand Down Expand Up @@ -102,6 +117,21 @@ func (r *runner) runSingleContainer(
}
}

userConfig, err := config.ParseDevContainerUserJSON(parsedConfig.Config)
if err != nil {
return nil, err
} else if userConfig != nil {
config.AddConfigToImageMetadata(userConfig, buildInfo.ImageMetadata)
}

for _, v := range options.ExtraDevContainerPaths {
extraConfig, err := config.ParseDevContainerJSONFile(v)
if err != nil {
return nil, err
}
config.AddConfigToImageMetadata(extraConfig, buildInfo.ImageMetadata)
}

// merge configuration
mergedConfig, err = config.MergeConfiguration(parsedConfig.Config, buildInfo.ImageMetadata.Config)
if err != nil {
Expand Down
1 change: 1 addition & 0 deletions pkg/provider/workspace.go
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,7 @@ type CLIOptions struct {
GitCloneStrategy git.CloneStrategy `json:"gitCloneStrategy,omitempty"`
FallbackImage string `json:"fallbackImage,omitempty"`
GitSSHSigningKey string `json:"gitSshSigningKey,omitempty"`
ExtraDevContainerPaths []string `json:"extraDevContainerPaths,omitempty"`

// build options
Repository string `json:"repository,omitempty"`
Expand Down
12 changes: 12 additions & 0 deletions vendor/dario.cat/mergo/.deepsource.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
version = 1

test_patterns = [
"*_test.go"
]

[[analyzers]]
name = "go"
enabled = true

[analyzers.meta]
import_path = "dario.cat/mergo"
36 changes: 36 additions & 0 deletions vendor/dario.cat/mergo/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
#### joe made this: http://goel.io/joe

#### go ####
# Binaries for programs and plugins
*.exe
*.dll
*.so
*.dylib

# Test binary, build with `go test -c`
*.test

# Output of the go coverage tool, specifically when used with LiteIDE
*.out

# Golang/Intellij
.idea

# Project-local glide cache, RE: https://github.com/Masterminds/glide/issues/736
.glide/

#### vim ####
# Swap
[._]*.s[a-v][a-z]
[._]*.sw[a-p]
[._]s[a-v][a-z]
[._]sw[a-p]

# Session
Session.vim

# Temporary
.netrwhist
*~
# Auto-generated tag files
tags
12 changes: 12 additions & 0 deletions vendor/dario.cat/mergo/.travis.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
language: go
arch:
- amd64
- ppc64le
install:
- go get -t
- go get golang.org/x/tools/cmd/cover
- go get github.com/mattn/goveralls
script:
- go test -race -v ./...
after_script:
- $HOME/gopath/bin/goveralls -service=travis-ci -repotoken $COVERALLS_TOKEN
Loading

0 comments on commit d2d6d9a

Please sign in to comment.