Skip to content

Commit

Permalink
feat: ensure _REMOTE_USER is set for feature install (#67)
Browse files Browse the repository at this point in the history
  • Loading branch information
coryb authored Nov 8, 2023
1 parent cbbf185 commit 6a287b9
Show file tree
Hide file tree
Showing 3 changed files with 30 additions and 18 deletions.
21 changes: 13 additions & 8 deletions devcontainer/devcontainer.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,11 @@ func Parse(content []byte) (*Spec, error) {
}

type Spec struct {
Image string `json:"image"`
Build BuildSpec `json:"build"`
RemoteUser string `json:"remoteUser"`
RemoteEnv map[string]string `json:"remoteEnv"`
Image string `json:"image"`
Build BuildSpec `json:"build"`
RemoteUser string `json:"remoteUser"`
ContainerUser string `json:"containerUser"`
RemoteEnv map[string]string `json:"remoteEnv"`
// Features is a map of feature names to feature configurations.
Features map[string]any `json:"features"`
LifecycleScripts
Expand Down Expand Up @@ -89,7 +90,7 @@ func (s *Spec) Compile(fs billy.Filesystem, devcontainerDir, scratchDir, fallbac
env = append(env, key+"="+value)
}
params := &Compiled{
User: s.RemoteUser,
User: s.ContainerUser,
Env: env,
}

Expand Down Expand Up @@ -164,14 +165,18 @@ func (s *Spec) Compile(fs billy.Filesystem, devcontainerDir, scratchDir, fallbac
return nil, fmt.Errorf("get user from image: %w", err)
}
}
params.DockerfileContent, err = s.compileFeatures(fs, scratchDir, params.User, params.DockerfileContent)
remoteUser := s.RemoteUser
if remoteUser == "" {
remoteUser = params.User
}
params.DockerfileContent, err = s.compileFeatures(fs, scratchDir, params.User, remoteUser, params.DockerfileContent)
if err != nil {
return nil, err
}
return params, nil
}

func (s *Spec) compileFeatures(fs billy.Filesystem, scratchDir, remoteUser, dockerfileContent string) (string, error) {
func (s *Spec) compileFeatures(fs billy.Filesystem, scratchDir, containerUser, remoteUser, dockerfileContent string) (string, error) {
// If there are no features, we don't need to do anything!
if len(s.Features) == 0 {
return dockerfileContent, nil
Expand Down Expand Up @@ -227,7 +232,7 @@ func (s *Spec) compileFeatures(fs billy.Filesystem, scratchDir, remoteUser, dock
if err != nil {
return "", fmt.Errorf("extract feature %s: %w", featureRefRaw, err)
}
directive, err := spec.Compile(featureOpts)
directive, err := spec.Compile(containerUser, remoteUser, featureOpts)
if err != nil {
return "", fmt.Errorf("compile feature %s: %w", featureRefRaw, err)
}
Expand Down
13 changes: 10 additions & 3 deletions devcontainer/features/features.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"path/filepath"
"regexp"
"sort"
"strconv"
"strings"

"github.com/go-git/go-billy/v5"
Expand Down Expand Up @@ -169,8 +170,14 @@ type Spec struct {

// Extract unpacks the feature from the image and returns a set of lines
// that should be appended to a Dockerfile to install the feature.
func (s *Spec) Compile(options map[string]any) (string, error) {
var runDirective []string
func (s *Spec) Compile(containerUser, remoteUser string, options map[string]any) (string, error) {
// TODO not sure how we figure out _(REMOTE|CONTAINER)_USER_HOME
// as per the feature spec.
// See https://containers.dev/implementors/features/#user-env-var
runDirective := []string{
"_CONTAINER_USER=" + strconv.Quote(containerUser),
"_REMOTE_USER=" + strconv.Quote(remoteUser),
}
for key, value := range s.Options {
strValue := fmt.Sprint(value.Default)
provided, ok := options[key]
Expand All @@ -179,7 +186,7 @@ func (s *Spec) Compile(options map[string]any) (string, error) {
// delete so we can check if there are any unknown options
delete(options, key)
}
runDirective = append(runDirective, fmt.Sprintf(`%s="%s"`, convertOptionNameToEnv(key), strValue))
runDirective = append(runDirective, fmt.Sprintf(`%s=%q`, convertOptionNameToEnv(key), strValue))
}
if len(options) > 0 {
return "", fmt.Errorf("unknown option: %v", options)
Expand Down
14 changes: 7 additions & 7 deletions devcontainer/features/features_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ func TestCompile(t *testing.T) {
t.Run("UnknownOption", func(t *testing.T) {
t.Parallel()
spec := &features.Spec{}
_, err := spec.Compile(map[string]any{
_, err := spec.Compile("containerUser", "remoteUser", map[string]any{
"unknown": "value",
})
require.ErrorContains(t, err, "unknown option")
Expand All @@ -83,9 +83,9 @@ func TestCompile(t *testing.T) {
spec := &features.Spec{
Directory: "/",
}
directive, err := spec.Compile(nil)
directive, err := spec.Compile("containerUser", "remoteUser", nil)
require.NoError(t, err)
require.Equal(t, "WORKDIR /\nRUN ./install.sh", strings.TrimSpace(directive))
require.Equal(t, "WORKDIR /\nRUN _CONTAINER_USER=\"containerUser\" _REMOTE_USER=\"remoteUser\" ./install.sh", strings.TrimSpace(directive))
})
t.Run("ContainerEnv", func(t *testing.T) {
t.Parallel()
Expand All @@ -95,9 +95,9 @@ func TestCompile(t *testing.T) {
"FOO": "bar",
},
}
directive, err := spec.Compile(nil)
directive, err := spec.Compile("containerUser", "remoteUser", nil)
require.NoError(t, err)
require.Equal(t, "WORKDIR /\nENV FOO=bar\nRUN ./install.sh", strings.TrimSpace(directive))
require.Equal(t, "WORKDIR /\nENV FOO=bar\nRUN _CONTAINER_USER=\"containerUser\" _REMOTE_USER=\"remoteUser\" ./install.sh", strings.TrimSpace(directive))
})
t.Run("OptionsEnv", func(t *testing.T) {
t.Parallel()
Expand All @@ -109,8 +109,8 @@ func TestCompile(t *testing.T) {
},
},
}
directive, err := spec.Compile(nil)
directive, err := spec.Compile("containerUser", "remoteUser", nil)
require.NoError(t, err)
require.Equal(t, "WORKDIR /\nRUN FOO=\"bar\" ./install.sh", strings.TrimSpace(directive))
require.Equal(t, "WORKDIR /\nRUN FOO=\"bar\" _CONTAINER_USER=\"containerUser\" _REMOTE_USER=\"remoteUser\" ./install.sh", strings.TrimSpace(directive))
})
}

0 comments on commit 6a287b9

Please sign in to comment.