Skip to content

Commit

Permalink
fix: Use fallback image if devcontainer.json doesn't specify an image…
Browse files Browse the repository at this point in the history
… or Dockerfile (#30)

Co-authored-by: Kyle Carberry <[email protected]>
  • Loading branch information
aaronlehmann and kylecarbs authored Jul 14, 2023
1 parent 2bb2298 commit febe5b3
Show file tree
Hide file tree
Showing 4 changed files with 56 additions and 17 deletions.
19 changes: 17 additions & 2 deletions devcontainer/devcontainer.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,11 +56,22 @@ type Compiled struct {
Env []string
}

// HasImage returns true if the devcontainer.json specifies an image.
func (s Spec) HasImage() bool {
return s.Image != ""
}

// HasDockerfile returns true if the devcontainer.json specifies the path to a
// Dockerfile.
func (s Spec) HasDockerfile() bool {
return s.Dockerfile != "" || s.Build.Dockerfile != ""
}

// Compile returns the build parameters for the workspace.
// devcontainerDir is the path to the directory where the devcontainer.json file
// is located. scratchDir is the path to the directory where the Dockerfile will
// be written to if one doesn't exist.
func (s *Spec) Compile(fs billy.Filesystem, devcontainerDir, scratchDir string) (*Compiled, error) {
func (s *Spec) Compile(fs billy.Filesystem, devcontainerDir, scratchDir, fallbackDockerfile string) (*Compiled, error) {
env := make([]string, 0)
for key, value := range s.RemoteEnv {
env = append(env, key+"="+value)
Expand Down Expand Up @@ -93,7 +104,11 @@ func (s *Spec) Compile(fs billy.Filesystem, devcontainerDir, scratchDir string)
s.Build.Context = s.Context
}

params.DockerfilePath = filepath.Join(devcontainerDir, s.Build.Dockerfile)
if s.Build.Dockerfile != "" {
params.DockerfilePath = filepath.Join(devcontainerDir, s.Build.Dockerfile)
} else {
params.DockerfilePath = fallbackDockerfile
}
params.BuildContext = filepath.Join(devcontainerDir, s.Build.Context)
}

Expand Down
4 changes: 2 additions & 2 deletions devcontainer/devcontainer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ func TestCompileDevContainer(t *testing.T) {
dc := &devcontainer.Spec{
Image: "codercom/code-server:latest",
}
params, err := dc.Compile(fs, "", envbuilder.MagicDir)
params, err := dc.Compile(fs, "", envbuilder.MagicDir, "")
require.NoError(t, err)
require.Equal(t, filepath.Join(envbuilder.MagicDir, "Dockerfile"), params.DockerfilePath)
require.Equal(t, envbuilder.MagicDir, params.BuildContext)
Expand All @@ -69,7 +69,7 @@ func TestCompileDevContainer(t *testing.T) {
_, err = io.WriteString(file, "FROM ubuntu")
require.NoError(t, err)
_ = file.Close()
params, err := dc.Compile(fs, dcDir, envbuilder.MagicDir)
params, err := dc.Compile(fs, dcDir, envbuilder.MagicDir, "")
require.NoError(t, err)
require.Equal(t, "ARG1=value1", params.BuildArgs[0])
require.Equal(t, filepath.Join(dcDir, "Dockerfile"), params.DockerfilePath)
Expand Down
34 changes: 21 additions & 13 deletions envbuilder.go
Original file line number Diff line number Diff line change
Expand Up @@ -314,36 +314,34 @@ func Run(ctx context.Context, options Options) error {
}
}

var buildParams *devcontainer.Compiled

defaultBuildParams := func() error {
defaultBuildParams := func() (*devcontainer.Compiled, error) {
dockerfile := filepath.Join(MagicDir, "Dockerfile")
file, err := options.Filesystem.OpenFile(dockerfile, os.O_CREATE|os.O_WRONLY, 0644)
if err != nil {
return err
return nil, err
}
defer file.Close()
if options.FallbackImage == "" {
if fallbackErr != nil {
return xerrors.Errorf("%s: %w", fallbackErr.Error(), ErrNoFallbackImage)
return nil, xerrors.Errorf("%s: %w", fallbackErr.Error(), ErrNoFallbackImage)
}
// We can't use errors.Join here because our tests
// don't support parsing a multiline error.
return ErrNoFallbackImage
return nil, ErrNoFallbackImage
}
content := "FROM " + options.FallbackImage
_, err = file.Write([]byte(content))
if err != nil {
return err
return nil, err
}
buildParams = &devcontainer.Compiled{
return &devcontainer.Compiled{
DockerfilePath: dockerfile,
DockerfileContent: content,
BuildContext: MagicDir,
}
return nil
}, nil
}

var buildParams *devcontainer.Compiled
if options.DockerfilePath == "" {
// Only look for a devcontainer if a Dockerfile wasn't specified.
// devcontainer is a standard, so it's reasonable to be the default.
Expand All @@ -364,7 +362,16 @@ func Run(ctx context.Context, options Options) error {
}
devContainer, err := devcontainer.Parse(content)
if err == nil {
buildParams, err = devContainer.Compile(options.Filesystem, devcontainerDir, MagicDir)
var fallbackDockerfile string
if !devContainer.HasImage() && !devContainer.HasDockerfile() {
defaultParams, err := defaultBuildParams()
if err != nil {
return fmt.Errorf("no Dockerfile or image found: %w", err)
}
logf(codersdk.LogLevelInfo, "No Dockerfile or image specified; falling back to the default image...")
fallbackDockerfile = defaultParams.DockerfilePath
}
buildParams, err = devContainer.Compile(options.Filesystem, devcontainerDir, MagicDir, fallbackDockerfile)
if err != nil {
return fmt.Errorf("compile devcontainer.json: %w", err)
}
Expand Down Expand Up @@ -393,7 +400,8 @@ func Run(ctx context.Context, options Options) error {
if buildParams == nil {
// If there isn't a devcontainer.json file in the repository,
// we fallback to whatever the `DefaultImage` is.
err := defaultBuildParams()
var err error
buildParams, err = defaultBuildParams()
if err != nil {
return fmt.Errorf("no Dockerfile or devcontainer.json found: %w", err)
}
Expand Down Expand Up @@ -543,7 +551,7 @@ func Run(ctx context.Context, options Options) error {
}
logf(codersdk.LogLevelError, "Failed to build: %s", err)
logf(codersdk.LogLevelError, "Falling back to the default image...")
err = defaultBuildParams()
buildParams, err = defaultBuildParams()
if err != nil {
return err
}
Expand Down
16 changes: 16 additions & 0 deletions integration/integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -264,6 +264,22 @@ RUN exit 1`,
})
require.ErrorContains(t, err, envbuilder.ErrNoFallbackImage.Error())
})
t.Run("NoImageOrDockerfile", func(t *testing.T) {
t.Parallel()
url := createGitServer(t, gitServerOptions{
files: map[string]string{
".devcontainer/devcontainer.json": "{}",
},
})
ctr, err := runEnvbuilder(t, []string{
"GIT_URL=" + url,
"FALLBACK_IMAGE=alpine:latest",
})
require.NoError(t, err)

output := execContainer(t, ctr, "echo hello")
require.Equal(t, "hello", strings.TrimSpace(output))
})
}

func TestPrivateRegistry(t *testing.T) {
Expand Down

0 comments on commit febe5b3

Please sign in to comment.