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
370 changes: 0 additions & 370 deletions cmd/up.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,26 +3,18 @@ package cmd
import (
"context"
"fmt"
"io"
"os"
"os/signal"
"path/filepath"
"syscall"

"github.com/devsy-org/devsy/cmd/flags"
"github.com/devsy-org/devsy/pkg/agent"
"github.com/devsy-org/devsy/pkg/agent/tunnelserver"
client2 "github.com/devsy-org/devsy/pkg/client"
"github.com/devsy-org/devsy/pkg/client/clientimplementation"
"github.com/devsy-org/devsy/pkg/config"
config2 "github.com/devsy-org/devsy/pkg/devcontainer/config"
"github.com/devsy-org/devsy/pkg/devcontainer/sshtunnel"
"github.com/devsy-org/devsy/pkg/dotfiles"
"github.com/devsy-org/devsy/pkg/ide"
"github.com/devsy-org/devsy/pkg/ide/opener"
"github.com/devsy-org/devsy/pkg/log"
provider2 "github.com/devsy-org/devsy/pkg/provider"
devssh "github.com/devsy-org/devsy/pkg/ssh"
"github.com/devsy-org/devsy/pkg/telemetry"
"github.com/devsy-org/devsy/pkg/util"
"github.com/spf13/cobra"
Expand Down Expand Up @@ -219,368 +211,6 @@ func (cmd *UpCmd) executeDevsyUp(
return &workspaceContext{result: result, user: user, workdir: workdir}, nil
}

// configureWorkspace sets up SSH, Git, and dotfiles.
func (cmd *UpCmd) configureWorkspace(
devsyConfig *config.Config,
client client2.BaseWorkspaceClient,
wctx *workspaceContext,
) error {
if cmd.ConfigureSSH {
devsyHome := ""
if envDevsyHome, ok := os.LookupEnv(config.EnvHome); ok {
devsyHome = envDevsyHome
}
setupGPGAgentForwarding := cmd.GPGAgentForwarding ||
devsyConfig.ContextOption(config.ContextOptionGPGAgentForwarding) == config.BoolTrue
sshConfigIncludePath := devsyConfig.ContextOption(config.ContextOptionSSHConfigIncludePath)

if err := configureSSH(client, configureSSHParams{
sshConfigPath: cmd.SSHConfigPath,
sshConfigIncludePath: sshConfigIncludePath,
user: wctx.user,
workdir: wctx.workdir,
gpgagent: setupGPGAgentForwarding,
devsyHome: devsyHome,
}); err != nil {
return err
}

log.Info("SSH configuration completed in workspace")
}

// Dotfiles are now installed in-container during the lifecycle
// (between postCreateCommand and postStartCommand). The host-side
// SSH-based installation is only used as a fallback when the
// container-side path was not configured.
if cmd.DotfilesRepo == "" {
if err := dotfiles.Setup(dotfiles.SetupParams{
Source: cmd.DotfilesSource,
Script: cmd.DotfilesScript,
EnvFiles: cmd.DotfilesScriptEnvFile,
EnvKeyValues: cmd.DotfilesScriptEnv,
Client: client,
DevsyConfig: devsyConfig,
}); err != nil {
return err
}
}

return nil
}

// openIDE opens the configured IDE.
func (cmd *UpCmd) openIDE(
ctx context.Context,
devsyConfig *config.Config,
client client2.BaseWorkspaceClient,
wctx *workspaceContext,
) error {
if !cmd.OpenIDE {
return nil
}

ideConfig := client.WorkspaceConfig().IDE
return opener.Open(ctx, ideConfig.Name, ideConfig.Options, opener.Params{
GPGAgentForwarding: cmd.GPGAgentForwarding,
SSHAuthSockID: cmd.SSHAuthSockID,
GitSSHSigningKey: cmd.GitSSHSigningKey,
DevsyConfig: devsyConfig,
Client: client,
User: wctx.user,
Result: wctx.result,
})
}

func (cmd *UpCmd) devsyUp(
ctx context.Context,
devsyConfig *config.Config,
client client2.BaseWorkspaceClient,
) (*config2.Result, error) {
var err error

// only lock if we are not in platform mode
if !cmd.Platform.Enabled {
err := client.Lock(ctx)
if err != nil {
return nil, fmt.Errorf("lock workspace: %w", err)
}
defer client.Unlock()
}

// get result
var result *config2.Result

switch client := client.(type) {
case client2.WorkspaceClient:
result, err = cmd.devsyUpMachine(ctx, devsyConfig, client)
if err != nil {
return nil, err
}
case client2.ProxyClient:
result, err = cmd.devsyUpProxy(ctx, client)
if err != nil {
return nil, err
}
case client2.DaemonClient:
result, err = cmd.devsyUpDaemon(ctx, client)
if err != nil {
return nil, err
}
default:
return nil, fmt.Errorf("unsupported client type: %T", client)
}

// save result to file
err = provider2.SaveWorkspaceResult(client.WorkspaceConfig(), result)
if err != nil {
return nil, fmt.Errorf("save workspace result: %w", err)
}

return result, nil
}

func (cmd *UpCmd) devsyUpProxy(
ctx context.Context,
client client2.ProxyClient,
) (*config2.Result, error) {
// create pipes
stdoutReader, stdoutWriter, err := os.Pipe()
if err != nil {
return nil, err
}
stdinReader, stdinWriter, err := os.Pipe()
if err != nil {
return nil, err
}
defer func() { _ = stdoutWriter.Close() }()
defer func() { _ = stdinWriter.Close() }()

// start machine on stdio
cancelCtx, cancel := context.WithCancel(ctx)
defer cancel()

// create up command
errChan := make(chan error, 1)
go func() {
defer log.Debug("done executing up command")
defer cancel()

// build devsy up options
workspace := client.WorkspaceConfig()
baseOptions := cmd.CLIOptions
baseOptions.ID = workspace.ID
baseOptions.DevContainerPath = workspace.DevContainerPath
baseOptions.DevContainerImage = workspace.DevContainerImage
baseOptions.IDE = workspace.IDE.Name
baseOptions.IDEOptions = nil
baseOptions.Source = workspace.Source.String()
for optionName, optionValue := range workspace.IDE.Options {
baseOptions.IDEOptions = append(
baseOptions.IDEOptions,
optionName+"="+optionValue.Value,
)
}

// run devsy up elsewhere
err = client.Up(ctx, client2.UpOptions{
CLIOptions: baseOptions,
Debug: cmd.Debug,

Stdin: stdinReader,
Stdout: stdoutWriter,
})
if err != nil {
errChan <- fmt.Errorf("executing up proxy command: %w", err)
} else {
errChan <- nil
}
}()

// create container etc.
result, err := tunnelserver.RunUpServer(
cancelCtx,
stdoutReader,
stdinWriter,
true,
true,
client.WorkspaceConfig(),
)
if err != nil {
return nil, fmt.Errorf("run tunnel machine: %w", err)
}

// wait until command finished
return result, <-errChan
}

func (cmd *UpCmd) devsyUpDaemon(
ctx context.Context,
client client2.DaemonClient,
) (*config2.Result, error) {
// build devsy up options
workspace := client.WorkspaceConfig()
baseOptions := cmd.CLIOptions
baseOptions.ID = workspace.ID
baseOptions.DevContainerPath = workspace.DevContainerPath
baseOptions.DevContainerImage = workspace.DevContainerImage
baseOptions.IDE = workspace.IDE.Name
baseOptions.IDEOptions = nil
baseOptions.Source = workspace.Source.String()
for optionName, optionValue := range workspace.IDE.Options {
baseOptions.IDEOptions = append(
baseOptions.IDEOptions,
optionName+"="+optionValue.Value,
)
}

// run devsy up elsewhere
return client.Up(ctx, client2.UpOptions{
CLIOptions: baseOptions,
Debug: cmd.Debug,
})
}

func (cmd *UpCmd) devsyUpMachine(
ctx context.Context,
devsyConfig *config.Config,
client client2.WorkspaceClient,
) (*config2.Result, error) {
err := clientimplementation.StartWait(ctx, client, true)
if err != nil {
return nil, fmt.Errorf("wait for machine: %w", err)
}

// compress info
workspaceInfo, wInfo, err := client.AgentInfo(cmd.CLIOptions)
if err != nil {
return nil, fmt.Errorf("get agent info: %w", err)
}

// create container etc.
log.Info("creating devcontainer")
defer log.Debug("done creating devcontainer")

// if we run on a platform, we need to pass the platform options
if cmd.Platform.Enabled {
return clientimplementation.BuildAgentClient(
ctx,
clientimplementation.BuildAgentClientOptions{
WorkspaceClient: client,
CLIOptions: cmd.CLIOptions,
AgentCommand: "up",
TunnelOptions: []tunnelserver.Option{
tunnelserver.WithPlatformOptions(&cmd.Platform),
},
},
)
}

// ssh tunnel command
sshTunnelCmd := fmt.Sprintf("'%s' helper ssh-server --stdio", client.AgentPath())
if log.DebugEnabled() {
sshTunnelCmd += " --debug"
}

// create agent command
agentCommand := fmt.Sprintf(
"'%s' agent workspace up --workspace-info '%s'",
client.AgentPath(),
workspaceInfo,
)

if log.DebugEnabled() {
agentCommand += " --debug"
}

agentInjectFunc := func(
cancelCtx context.Context, sshCmd string, sshTunnelStdinReader, sshTunnelStdoutWriter *os.File,
writer io.WriteCloser,
) error {
return agent.InjectAgent(&agent.InjectOptions{
Ctx: cancelCtx,
Exec: func(ctx context.Context, command string, stdin io.Reader, stdout io.Writer, stderr io.Writer) error {
return client.Command(ctx, client2.CommandOptions{
Command: command,
Stdin: stdin,
Stdout: stdout,
Stderr: stderr,
})
},
IsLocal: client.AgentLocal(),
RemoteAgentPath: client.AgentPath(),
DownloadURL: client.AgentURL(),
Command: sshCmd,
Stdin: sshTunnelStdinReader,
Stdout: sshTunnelStdoutWriter,
Stderr: writer,
Timeout: wInfo.InjectTimeout,
})
}

return sshtunnel.ExecuteCommand(ctx, sshtunnel.ExecuteCommandOptions{
Client: client,
AddPrivateKeys: devsyConfig.ContextOption(
config.ContextOptionSSHAddPrivateKeys,
) == config.BoolTrue,
AgentInject: agentInjectFunc,
SSHCommand: sshTunnelCmd,
Command: agentCommand,
TunnelServerFunc: func(ctx context.Context, stdin io.WriteCloser, stdout io.Reader) (*config2.Result, error) {
return tunnelserver.RunUpServer(
ctx,
stdout,
stdin,
client.AgentInjectGitCredentials(cmd.CLIOptions),
client.AgentInjectDockerCredentials(cmd.CLIOptions),
client.WorkspaceConfig(),
)
},
})
}

type configureSSHParams struct {
sshConfigPath string
sshConfigIncludePath string
user string
workdir string
gpgagent bool
devsyHome string
}

func configureSSH(client client2.BaseWorkspaceClient, params configureSSHParams) error {
path, err := devssh.ResolveSSHConfigPath(params.sshConfigPath)
if err != nil {
return fmt.Errorf("invalid ssh config path: %w", err)
}
sshConfigPath := path

sshConfigIncludePath := params.sshConfigIncludePath
if sshConfigIncludePath != "" {
includePath, err := devssh.ResolveSSHConfigPath(sshConfigIncludePath)
if err != nil {
return fmt.Errorf("invalid ssh config include path: %w", err)
}
sshConfigIncludePath = includePath
}

err = devssh.ConfigureSSHConfig(devssh.SSHConfigParams{
SSHConfigPath: sshConfigPath,
SSHConfigIncludePath: sshConfigIncludePath,
Context: client.Context(),
Workspace: client.Workspace(),
User: params.user,
Workdir: params.workdir,
GPGAgent: params.gpgagent,
DevsyHome: params.devsyHome,
Provider: client.Provider(),
})
if err != nil {
return err
}

return nil
}

func WithSignals(ctx context.Context) (context.Context, func()) {
ctx, cancel := context.WithCancel(ctx)
signals := make(chan os.Signal, 1)
Expand Down
Loading
Loading