Skip to content

Added --root-user flag to devbox generate #1359

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 5 commits into from
Aug 11, 2023
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 devbox.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,8 @@ type Devbox interface {
// Generate creates the directory of Nix files and the Dockerfile that define
// the devbox environment.
Generate(ctx context.Context) error
GenerateDevcontainer(ctx context.Context, force bool) error
GenerateDockerfile(ctx context.Context, force bool) error
GenerateDevcontainer(ctx context.Context, generateOpts devopt.GenerateOpts) error
GenerateDockerfile(ctx context.Context, generateOpts devopt.GenerateOpts) error
GenerateEnvrcFile(ctx context.Context, force bool, envFlags devopt.EnvFlags) error
Info(ctx context.Context, pkg string, markdown bool) error
Install(ctx context.Context) error
Expand Down
13 changes: 11 additions & 2 deletions internal/boxcli/generate.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ type generateCmdFlags struct {
force bool
printEnvrcContent bool
githubUsername string
rootUser bool
}

func generateCmd() *cobra.Command {
Expand Down Expand Up @@ -64,6 +65,8 @@ func devcontainerCmd() *cobra.Command {
}
command.Flags().BoolVarP(
&flags.force, "force", "f", false, "force overwrite on existing files")
command.Flags().BoolVar(
&flags.rootUser, "root-user", false, "Use root as default user inside the container")
return command
}

Expand All @@ -81,6 +84,8 @@ func dockerfileCmd() *cobra.Command {
}
command.Flags().BoolVarP(
&flags.force, "force", "f", false, "force overwrite existing files")
command.Flags().BoolVar(
&flags.rootUser, "root-user", false, "Use root as default user inside the container")
flags.config.register(command)
return command
}
Expand Down Expand Up @@ -139,13 +144,17 @@ func runGenerateCmd(cmd *cobra.Command, flags *generateCmdFlags) error {
if err != nil {
return errors.WithStack(err)
}
generateOpts := devopt.GenerateOpts{
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hack, if you embed devopt.GenerateOpts to the flag struct you can pass flags.GenerateOpts into the functions below.

Force: flags.force,
RootUser: flags.rootUser,
}
switch cmd.Use {
case "debug":
return box.Generate(cmd.Context())
case "devcontainer":
return box.GenerateDevcontainer(cmd.Context(), flags.force)
return box.GenerateDevcontainer(cmd.Context(), generateOpts)
case "dockerfile":
return box.GenerateDockerfile(cmd.Context(), flags.force)
return box.GenerateDockerfile(cmd.Context(), generateOpts)
}
return nil
}
Expand Down
36 changes: 26 additions & 10 deletions internal/impl/devbox.go
Original file line number Diff line number Diff line change
Expand Up @@ -370,7 +370,7 @@ func (d *Devbox) Info(ctx context.Context, pkg string, markdown bool) error {

// GenerateDevcontainer generates devcontainer.json and Dockerfile for vscode run-in-container
// and GitHub Codespaces
func (d *Devbox) GenerateDevcontainer(ctx context.Context, force bool) error {
func (d *Devbox) GenerateDevcontainer(ctx context.Context, generateOpts devopt.GenerateOpts) error {
ctx, task := trace.NewTask(ctx, "devboxGenerateDevcontainer")
defer task.End()

Expand All @@ -381,7 +381,7 @@ func (d *Devbox) GenerateDevcontainer(ctx context.Context, force bool) error {

// check if devcontainer.json or Dockerfile exist
filesExist := fileutil.Exists(devContainerJSONPath) || fileutil.Exists(dockerfilePath)
if !force && filesExist {
if !generateOpts.Force && filesExist {
return usererr.New(
"Files devcontainer.json or Dockerfile are already present in .devcontainer/. " +
"Remove the files or use --force to overwrite them.",
Expand All @@ -394,15 +394,24 @@ func (d *Devbox) GenerateDevcontainer(ctx context.Context, force bool) error {
return redact.Errorf("error creating dev container directory in <project>/%s: %w",
redact.Safe(filepath.Base(devContainerPath)), err)
}

// Setup generate parameters
gen := &generate.Options{
Path: devContainerPath,
RootUser: generateOpts.RootUser,
IsDevcontainer: true,
Pkgs: d.Packages(),
LocalFlakeDirs: d.getLocalFlakesDirs(),
}

// generate dockerfile
err = generate.CreateDockerfile(ctx,
devContainerPath, d.getLocalFlakesDirs(), true /* isDevcontainer */)
err = gen.CreateDockerfile(ctx)
if err != nil {
return redact.Errorf("error generating dev container Dockerfile in <project>/%s: %w",
redact.Safe(filepath.Base(devContainerPath)), err)
}
// generate devcontainer.json
err = generate.CreateDevcontainer(ctx, devContainerPath, d.Packages())
err = gen.CreateDevcontainer(ctx)
if err != nil {
return redact.Errorf("error generating devcontainer.json in <project>/%s: %w",
redact.Safe(filepath.Base(devContainerPath)), err)
Expand All @@ -411,24 +420,31 @@ func (d *Devbox) GenerateDevcontainer(ctx context.Context, force bool) error {
}

// GenerateDockerfile generates a Dockerfile that replicates the devbox shell
func (d *Devbox) GenerateDockerfile(ctx context.Context, force bool) error {
func (d *Devbox) GenerateDockerfile(ctx context.Context, generateOpts devopt.GenerateOpts) error {
ctx, task := trace.NewTask(ctx, "devboxGenerateDockerfile")
defer task.End()

dockerfilePath := filepath.Join(d.projectDir, "Dockerfile")
// check if Dockerfile doesn't exist
filesExist := fileutil.Exists(dockerfilePath)
if !force && filesExist {
if !generateOpts.Force && filesExist {
return usererr.New(
"Dockerfile is already present in the current directory. " +
"Remove it or use --force to overwrite it.",
)
}

// Setup Generate parameters
gen := &generate.Options{
Path: d.projectDir,
RootUser: generateOpts.RootUser,
IsDevcontainer: false,
Pkgs: d.Packages(),
LocalFlakeDirs: d.getLocalFlakesDirs(),
}

// generate dockerfile
return errors.WithStack(
generate.CreateDockerfile(ctx,
d.projectDir, d.getLocalFlakesDirs(), false /* isDevcontainer */))
return errors.WithStack(gen.CreateDockerfile(ctx))
}

func PrintEnvrcContent(w io.Writer, envFlags devopt.EnvFlags) error {
Expand Down
5 changes: 5 additions & 0 deletions internal/impl/devopt/devboxopts.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,11 @@ type Opts struct {
Writer io.Writer
}

type GenerateOpts struct {
Force bool
RootUser bool
}

type EnvFlags struct {
EnvMap map[string]string
EnvFile string
Expand Down
34 changes: 23 additions & 11 deletions internal/impl/generate/devcontainer_util.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,14 @@ import (
//go:embed tmpl/*
var tmplFS embed.FS

type Options struct {
Path string
RootUser bool
IsDevcontainer bool
Pkgs []string
LocalFlakeDirs []string
}

type devcontainerObject struct {
Name string `json:"name"`
Build *build `json:"build"`
Expand All @@ -48,15 +56,16 @@ type vscode struct {

type dockerfileData struct {
IsDevcontainer bool
RootUser bool
LocalFlakeDirs []string
}

// CreateDockerfile creates a Dockerfile in path and writes devcontainerDockerfile.tmpl's content into it
func CreateDockerfile(ctx context.Context, path string, localFlakeDirs []string, isDevcontainer bool) error {
func (g *Options) CreateDockerfile(ctx context.Context) error {
defer trace.StartRegion(ctx, "createDockerfile").End()

// create dockerfile
file, err := os.Create(filepath.Join(path, "Dockerfile"))
file, err := os.Create(filepath.Join(g.Path, "Dockerfile"))
if err != nil {
return err
}
Expand All @@ -66,23 +75,24 @@ func CreateDockerfile(ctx context.Context, path string, localFlakeDirs []string,
t := template.Must(template.ParseFS(tmplFS, "tmpl/"+tmplName))
// write content into file
return t.Execute(file, &dockerfileData{
IsDevcontainer: isDevcontainer,
LocalFlakeDirs: localFlakeDirs,
IsDevcontainer: g.IsDevcontainer,
RootUser: g.RootUser,
LocalFlakeDirs: g.LocalFlakeDirs,
})
}

// CreateDevcontainer creates a devcontainer.json in path and writes getDevcontainerContent's output into it
func CreateDevcontainer(ctx context.Context, path string, pkgs []string) error {
func (g *Options) CreateDevcontainer(ctx context.Context) error {
defer trace.StartRegion(ctx, "createDevcontainer").End()

// create devcontainer.json file
file, err := os.Create(filepath.Join(path, "devcontainer.json"))
file, err := os.Create(filepath.Join(g.Path, "devcontainer.json"))
if err != nil {
return err
}
defer file.Close()
// get devcontainer.json's content
devcontainerContent := getDevcontainerContent(pkgs)
devcontainerContent := g.getDevcontainerContent()
devcontainerFileBytes, err := json.MarshalIndent(devcontainerContent, "", " ")
if err != nil {
return err
Expand Down Expand Up @@ -121,7 +131,7 @@ func CreateEnvrc(ctx context.Context, path string, envFlags devopt.EnvFlags) err
})
}

func getDevcontainerContent(pkgs []string) *devcontainerObject {
func (g *Options) getDevcontainerContent() *devcontainerObject {
// object that gets written in devcontainer.json
devcontainerContent := &devcontainerObject{
// For format details, see https://aka.ms/devcontainer.json. For config options, see the README at:
Expand All @@ -142,8 +152,10 @@ func getDevcontainerContent(pkgs []string) *devcontainerObject {
},
},
},
// Comment out to connect as root instead. More info: https://aka.ms/vscode-remote/containers/non-root.
RemoteUser: "root",
RemoteUser: "devbox",
}
if g.RootUser {
devcontainerContent.RemoteUser = "root"
}

// match only python3 or python3xx as package names
Expand All @@ -152,7 +164,7 @@ func getDevcontainerContent(pkgs []string) *devcontainerObject {
debug.Log("Failed to compile regex")
return nil
}
for _, pkg := range pkgs {
for _, pkg := range g.Pkgs {
if py3pattern.MatchString(pkg) {
// Setup python3 interpreter path to devbox in the container
devcontainerContent.Customizations.Vscode.Settings = map[string]any{
Expand Down
22 changes: 21 additions & 1 deletion internal/impl/generate/tmpl/devcontainerDockerfile.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,38 @@ FROM debian:stable-slim
RUN apt-get update
RUN apt-get -y install bash binutils git{{if .IsDevcontainer}} gnupg2{{- end}} xz-utils wget sudo

{{- if not .RootUser }}

# Step 1.5: Setting up devbox user
ENV DEVBOX_USER=devbox
RUN adduser $DEVBOX_USER
RUN usermod -aG sudo $DEVBOX_USER
RUN echo "devbox ALL=(ALL:ALL) NOPASSWD: ALL" | sudo tee /etc/sudoers.d/$DEVBOX_USER
USER $DEVBOX_USER
{{- end}}

# Step 2: Installing Nix
RUN wget --output-document=/dev/stdout https://nixos.org/nix/install | sh -s -- --daemon
RUN wget --output-document=/dev/stdout https://nixos.org/nix/install | sh -s -- --{{if not .RootUser}}no-{{- end}}daemon
RUN . ~/.nix-profile/etc/profile.d/nix.sh
{{ if .RootUser }}
ENV PATH="/root/.nix-profile/bin:$PATH"
{{ else }}
ENV PATH="/home/${DEVBOX_USER}/.nix-profile/bin:$PATH"
{{- end}}

# Step 3: Installing devbox
RUN wget --quiet --output-document=/dev/stdout https://get.jetpack.io/devbox | bash -s -- -f
{{- if not .RootUser }}
RUN chown -R "${DEVBOX_USER}:${DEVBOX_USER}" /usr/local/bin/devbox
{{- end}}

# Step 4: Installing your devbox project
WORKDIR /code
COPY devbox.json devbox.json
COPY devbox.lock devbox.lock
{{- if not .RootUser }}
RUN sudo chown -R "${DEVBOX_USER}:${DEVBOX_USER}" /code
{{- end}}
{{if len .LocalFlakeDirs}}
# Step 6: Copying local flakes directories
{{- end}}
Expand Down