Skip to content

fix: Use fallback image if devcontainer.json doesn't specify an image or Dockerfile #30

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Jul 14, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
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 != ""
}

// HasImage 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 @@ -538,7 +546,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