Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions pkg/devcontainer/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -192,8 +192,8 @@ func (r *runner) substitute(
if parsedConfig.WorkspaceFolder != "" {
substitutionContext.ContainerWorkspaceFolder = parsedConfig.WorkspaceFolder
}
if parsedConfig.WorkspaceMount != "" {
substitutionContext.WorkspaceMount = parsedConfig.WorkspaceMount
if parsedConfig.WorkspaceMount != nil {
substitutionContext.WorkspaceMount = *parsedConfig.WorkspaceMount
}

if options.WorkspaceMountConsistency != "" {
Expand Down
3 changes: 2 additions & 1 deletion pkg/devcontainer/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -219,7 +219,8 @@ type NonComposeBase struct {
RunArgs []string `json:"runArgs,omitempty"`

// The --mount parameter for docker run. The default is to mount the project folder at /workspaces/$project.
WorkspaceMount string `json:"workspaceMount,omitempty"`
// Per the devcontainer spec, empty string suppresses the default workspace mount.
WorkspaceMount *string `json:"workspaceMount,omitempty"`
}

type DockerfileContainer struct {
Expand Down
2 changes: 1 addition & 1 deletion pkg/devcontainer/config/extends.go
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,7 @@ func mergeContainerScalars(result, child *DevContainerConfig) {
if child.WorkspaceFolder != "" {
result.WorkspaceFolder = child.WorkspaceFolder
}
if child.WorkspaceMount != "" {
if child.WorkspaceMount != nil {
result.WorkspaceMount = child.WorkspaceMount
}
if child.ShutdownAction != "" {
Expand Down
7 changes: 5 additions & 2 deletions pkg/devcontainer/config/result.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,11 @@ type DevContainerConfigWithPath struct {
}

func GetMounts(result *Result) []*Mount {
workspaceMount := ParseMount(result.SubstitutionContext.WorkspaceMount)
mounts := []*Mount{&workspaceMount}
var mounts []*Mount
if result.SubstitutionContext.WorkspaceMount != "" {
workspaceMount := ParseMount(result.SubstitutionContext.WorkspaceMount)
mounts = append(mounts, &workspaceMount)
}
for _, m := range result.MergedConfig.Mounts {
if m.Type == "bind" {
mounts = append(mounts, m)
Expand Down
2 changes: 1 addition & 1 deletion pkg/devcontainer/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -267,7 +267,7 @@ func (s *SubstituteTestSuite) TestSubstitute_WorkspaceMountConsistencyReplacesEx
rawConfig := &config.DevContainerConfig{
ImageContainer: config.ImageContainer{Image: "alpine:latest"},
NonComposeBase: config.NonComposeBase{
WorkspaceMount: "type=bind,source=/src,target=/ws,consistency='consistent'",
WorkspaceMount: ptr("type=bind,source=/src,target=/ws,consistency='consistent'"),
},
}
options := provider2.CLIOptions{
Expand Down
15 changes: 12 additions & 3 deletions pkg/devcontainer/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -350,9 +350,18 @@ func getWorkspace(
workspaceFolder, workspaceID string,
conf *config.DevContainerConfig,
) (string, string) {
if conf.WorkspaceMount != "" {
mount := config.ParseMount(conf.WorkspaceMount)
ws := conf.WorkspaceMount
if conf.WorkspaceMount != nil {
// Explicit empty string means suppress the workspace mount entirely.
if *conf.WorkspaceMount == "" {
containerMountFolder := conf.WorkspaceFolder
if containerMountFolder == "" {
containerMountFolder = "/workspaces/" + workspaceID
}
return "", containerMountFolder
}

mount := config.ParseMount(*conf.WorkspaceMount)
ws := *conf.WorkspaceMount
if needsDefaultConsistency() && !mountHasConsistency(ws) {
ws += ",consistency='consistent'"
}
Expand Down
23 changes: 19 additions & 4 deletions pkg/devcontainer/run_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -131,11 +131,13 @@ func searchString(s, substr string) bool {
return false
}

func strPtr(s string) *string { return &s }

func TestGetWorkspace_CustomWorkspaceMount(t *testing.T) {
customMount := "type=bind,source=/host/src,target=/custom-ws"
conf := &config.DevContainerConfig{
NonComposeBase: config.NonComposeBase{
WorkspaceMount: customMount,
WorkspaceMount: strPtr(customMount),
},
}

Expand Down Expand Up @@ -185,25 +187,38 @@ func TestGetWorkspace_DefaultMountWithWorkspaceFolder(t *testing.T) {
func TestGetWorkspace_EmptyWorkspaceMount(t *testing.T) {
conf := &config.DevContainerConfig{
NonComposeBase: config.NonComposeBase{
WorkspaceMount: "",
WorkspaceMount: strPtr(""),
},
}

mount, folder := getWorkspace("/home/user/project", "ws-id", conf)

if mount != "" {
t.Fatalf("expected empty mount string (suppressed), got %q", mount)
}
if folder != "/workspaces/ws-id" {
t.Fatalf("expected default folder, got %q", folder)
}
}

func TestGetWorkspace_NilWorkspaceMount(t *testing.T) {
conf := &config.DevContainerConfig{}

mount, folder := getWorkspace("/home/user/project", "ws-id", conf)

if !contains(mount, "type=bind") {
t.Fatalf("expected default bind mount, got %q", mount)
t.Fatalf("expected default bind mount for nil WorkspaceMount, got %q", mount)
}
if folder != "/workspaces/ws-id" {
t.Fatalf("expected default folder, got %q", folder)
}
}

func TestGetWorkspace_UserConsistencyPreserved(t *testing.T) {
customMount := "type=bind,source=/src,target=/ws,consistency=delegated"
conf := &config.DevContainerConfig{
NonComposeBase: config.NonComposeBase{
WorkspaceMount: customMount,
WorkspaceMount: strPtr(customMount),
},
}

Expand Down
20 changes: 14 additions & 6 deletions pkg/devcontainer/single.go
Original file line number Diff line number Diff line change
Expand Up @@ -428,8 +428,12 @@ func (r *runner) getDockerlessRunOptions(
substitutionContext *config.SubstitutionContext,
buildInfo *config.BuildInfo,
) (*driver.RunOptions, error) {
// parse workspace mount
workspaceMountParsed := config.ParseMount(substitutionContext.WorkspaceMount)
// parse workspace mount — nil when suppressed via workspaceMount: ""
var workspaceMountPtr *config.Mount
if substitutionContext.WorkspaceMount != "" {
parsed := config.ParseMount(substitutionContext.WorkspaceMount)
workspaceMountPtr = &parsed
}

// add metadata as label here
marshalled, err := metadata.MarshalImageMetadata(buildInfo.ImageMetadata.Raw)
Expand Down Expand Up @@ -496,7 +500,7 @@ func (r *runner) getDockerlessRunOptions(
},
Privileged: mergedConfig.Privileged,
Init: mergedConfig.Init,
WorkspaceMount: &workspaceMountParsed,
WorkspaceMount: workspaceMountPtr,
Mounts: mounts,
Userns: substitutionContext.Userns,
UidMap: substitutionContext.UidMap,
Expand All @@ -509,8 +513,12 @@ func (r *runner) getRunOptions(
substitutionContext *config.SubstitutionContext,
buildInfo *config.BuildInfo,
) (*driver.RunOptions, error) {
// parse workspace mount
workspaceMountParsed := config.ParseMount(substitutionContext.WorkspaceMount)
// parse workspace mount — nil when suppressed via workspaceMount: ""
var workspaceMountPtr *config.Mount
if substitutionContext.WorkspaceMount != "" {
parsed := config.ParseMount(substitutionContext.WorkspaceMount)
workspaceMountPtr = &parsed
}

// add metadata as label here
marshalled, err := metadata.MarshalImageMetadata(buildInfo.ImageMetadata.Raw)
Expand Down Expand Up @@ -553,7 +561,7 @@ func (r *runner) getRunOptions(
Labels: labels,
Privileged: mergedConfig.Privileged,
Init: mergedConfig.Init,
WorkspaceMount: &workspaceMountParsed,
WorkspaceMount: workspaceMountPtr,
SecurityOpt: mergedConfig.SecurityOpt,
Mounts: mergedConfig.Mounts,
Userns: substitutionContext.Userns,
Expand Down
5 changes: 5 additions & 0 deletions pkg/driver/kubernetes/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,11 @@ func (k *KubernetesDriver) runContainer(
) (err error) {
// get workspace mount
mount := options.WorkspaceMount
if mount == nil {
return fmt.Errorf(
"workspace mount is suppressed; cannot run in Kubernetes without a workspace mount",
)
}
if mount.Target == "" {
return fmt.Errorf("workspace mount target is empty")
}
Expand Down
Loading