From 17dea68b577863e7d81e665329b41c4654bbda6f Mon Sep 17 00:00:00 2001 From: Ansuman Satapathy Date: Mon, 18 Nov 2024 04:55:01 +0000 Subject: [PATCH] feat: [CDE-470]: use stream logger and buffer the results for gitspaces exec (#3008) * feat: [CDE-470]: set env variable for flavours * feat: [CDE-470]: set env variable for flavours * feat: [CDE-470]: set env variable for flavours * feat: [CDE-470]: set env variable for flavours * feat: [CDE-470]: format code * feat: [CDE-470]: format code * feat: [CDE-470]: format code * feat: [CDE-470]: use stream logger * feat: [CDE-470]: use stream logger * feat: [CDE-470]: use stream logger * feat: [CDE-470]: env variables for VS Code desktop --- app/gitspace/orchestrator/common/utils.go | 131 ++++++++++---- .../container/devcontainer_setup.go | 10 +- .../container/devcontainer_steps.go | 100 ++++++----- .../container/embedded_docker_provider.go | 13 +- .../orchestrator/devcontainer/exec.go | 167 +++++++++++------- app/gitspace/orchestrator/git/git.go | 12 +- app/gitspace/orchestrator/git/git_impl.go | 62 +++---- app/gitspace/orchestrator/ide/ide.go | 5 +- app/gitspace/orchestrator/ide/vscode.go | 42 ++--- app/gitspace/orchestrator/ide/vscodeweb.go | 62 ++++--- .../orchestrator/template/template.go | 4 + .../template/templates/set_env.sh | 29 +++ app/gitspace/orchestrator/user/user.go | 3 +- app/gitspace/orchestrator/user/user_impl.go | 20 ++- .../{orchestrator => }/types/types.go | 0 .../types/zerolog_adapter.go | 4 +- 16 files changed, 420 insertions(+), 244 deletions(-) create mode 100644 app/gitspace/orchestrator/template/templates/set_env.sh rename app/gitspace/{orchestrator => }/types/types.go (100%) rename app/gitspace/{orchestrator => }/types/zerolog_adapter.go (96%) diff --git a/app/gitspace/orchestrator/common/utils.go b/app/gitspace/orchestrator/common/utils.go index 2e5be8a55..f0e4c29a7 100644 --- a/app/gitspace/orchestrator/common/utils.go +++ b/app/gitspace/orchestrator/common/utils.go @@ -17,17 +17,24 @@ package common import ( "context" "fmt" + "strings" "github.com/harness/gitness/app/gitspace/orchestrator/devcontainer" "github.com/harness/gitness/app/gitspace/orchestrator/template" + "github.com/harness/gitness/app/gitspace/types" "github.com/harness/gitness/types/enum" ) const templateSupportedOSDistribution = "supported_os_distribution.sh" const templateVsCodeWebToolsInstallation = "install_tools_vs_code_web.sh" const templateVsCodeToolsInstallation = "install_tools_vs_code.sh" +const templateSetEnv = "set_env.sh" -func ValidateSupportedOS(ctx context.Context, exec *devcontainer.Exec) ([]byte, error) { +func ValidateSupportedOS( + ctx context.Context, + exec *devcontainer.Exec, + gitspaceLogger types.GitspaceLogger, +) error { // TODO: Currently not supporting arch, freebsd and alpine. // For alpine wee need to install multiple things from // https://github.com/microsoft/vscode/wiki/How-to-Contribute#prerequisites @@ -36,79 +43,127 @@ func ValidateSupportedOS(ctx context.Context, exec *devcontainer.Exec) ([]byte, OSInfoScript: osDetectScript, }) if err != nil { - return nil, fmt.Errorf("failed to generate scipt to validate supported os distribution from template %s: %w", + return fmt.Errorf("failed to generate scipt to validate supported os distribution from template %s: %w", templateSupportedOSDistribution, err) } - - output, err := exec.ExecuteCommandInHomeDirectory(ctx, script, true, false) + gitspaceLogger.Info("Validate supported OSes...") + err = ExecuteCommandInHomeDirAndLog(ctx, exec, script, true, gitspaceLogger) if err != nil { - return nil, fmt.Errorf("error while detecting os distribution: %w", err) + return fmt.Errorf("error while detecting os distribution: %w", err) } - return output, nil + return nil } -func InstallTools(ctx context.Context, exec *devcontainer.Exec, ideType enum.IDEType) ([]byte, error) { +func InstallTools( + ctx context.Context, + exec *devcontainer.Exec, + ideType enum.IDEType, + gitspaceLogger types.GitspaceLogger, +) error { switch ideType { case enum.IDETypeVSCodeWeb: - { - output, err := InstallToolsForVsCodeWeb(ctx, exec) - if err != nil { - return []byte(output), err - } - return []byte(output), nil + err := InstallToolsForVsCodeWeb(ctx, exec, gitspaceLogger) + if err != nil { + return err } + return nil case enum.IDETypeVSCode: - { - output, err := InstallToolsForVsCode(ctx, exec) - if err != nil { - return []byte(output), err - } - return []byte(output), nil + err := InstallToolsForVsCode(ctx, exec, gitspaceLogger) + if err != nil { + return err } + return nil } - return nil, nil + return nil } -func InstallToolsForVsCodeWeb(ctx context.Context, exec *devcontainer.Exec) (string, error) { +func InstallToolsForVsCodeWeb( + ctx context.Context, + exec *devcontainer.Exec, + gitspaceLogger types.GitspaceLogger, +) error { script, err := template.GenerateScriptFromTemplate( templateVsCodeWebToolsInstallation, &template.InstallToolsPayload{ OSInfoScript: osDetectScript, }) if err != nil { - return "", fmt.Errorf( + return fmt.Errorf( "failed to generate scipt to install tools for vs code web from template %s: %w", templateVsCodeWebToolsInstallation, err) } - output := "Installing tools for vs code web inside container\n" - _, err = exec.ExecuteCommandInHomeDirectory(ctx, script, true, false) + gitspaceLogger.Info("Installing tools for vs code web inside container") + gitspaceLogger.Info("Tools installation output...") + err = ExecuteCommandInHomeDirAndLog(ctx, exec, script, true, gitspaceLogger) if err != nil { - return "", fmt.Errorf("failed to install tools for vs code web: %w", err) + return fmt.Errorf("failed to install tools for vs code web: %w", err) } - - output += "Successfully installed tools for vs code web\n" - - return output, nil + gitspaceLogger.Info("Successfully installed tools for vs code web") + return nil } -func InstallToolsForVsCode(ctx context.Context, exec *devcontainer.Exec) (string, error) { +func InstallToolsForVsCode( + ctx context.Context, + exec *devcontainer.Exec, + gitspaceLogger types.GitspaceLogger, +) error { script, err := template.GenerateScriptFromTemplate( templateVsCodeToolsInstallation, &template.InstallToolsPayload{ OSInfoScript: osDetectScript, }) if err != nil { - return "", fmt.Errorf( + return fmt.Errorf( "failed to generate scipt to install tools for vs code from template %s: %w", templateVsCodeToolsInstallation, err) } - output := "Installing tools for vs code inside container\n" - _, err = exec.ExecuteCommandInHomeDirectory(ctx, script, true, false) + gitspaceLogger.Info("Installing tools for vs code in container") + err = ExecuteCommandInHomeDirAndLog(ctx, exec, script, true, gitspaceLogger) if err != nil { - return "", fmt.Errorf("failed to install tools for vs code: %w", err) + return fmt.Errorf("failed to install tools for vs code: %w", err) } - - output += "Successfully installed tools for vs code\n" - - return output, nil + gitspaceLogger.Info("Successfully installed tools for vs code") + return nil +} + +func SetEnv( + ctx context.Context, + exec *devcontainer.Exec, + gitspaceLogger types.GitspaceLogger, + environment []string, +) error { + // Join the elements with a newline character + result := strings.Join(environment, "\n") + script, err := template.GenerateScriptFromTemplate( + templateSetEnv, &template.SetEnvPayload{ + EnvVariables: result, + }) + if err != nil { + return fmt.Errorf("failed to generate scipt to set env from template %s: %w", + templateSetEnv, err) + } + gitspaceLogger.Info("Setting env...") + err = ExecuteCommandInHomeDirAndLog(ctx, exec, script, true, gitspaceLogger) + if err != nil { + return fmt.Errorf("error while setting env vars: %w", err) + } + return nil +} + +func ExecuteCommandInHomeDirAndLog( + ctx context.Context, + exec *devcontainer.Exec, + script string, + root bool, + gitspaceLogger types.GitspaceLogger, +) error { + outputCh := make(chan []byte) + err := exec.ExecuteCommandInHomeDirectory(ctx, script, root, false, outputCh) + for output := range outputCh { + // Log output from the command as a string + if len(output) > 0 { + gitspaceLogger.Info(string(output)) + } + } + return err } diff --git a/app/gitspace/orchestrator/container/devcontainer_setup.go b/app/gitspace/orchestrator/container/devcontainer_setup.go index 6c8242652..cbb55da64 100644 --- a/app/gitspace/orchestrator/container/devcontainer_setup.go +++ b/app/gitspace/orchestrator/container/devcontainer_setup.go @@ -22,7 +22,7 @@ import ( "strconv" "strings" - orchestratorTypes "github.com/harness/gitness/app/gitspace/orchestrator/types" + gitspaceTypes "github.com/harness/gitness/app/gitspace/types" "github.com/harness/gitness/types" "github.com/docker/docker/api/types/container" @@ -47,7 +47,7 @@ var containerStateMapping = map[string]State{ } // Helper function to log messages and handle error wrapping. -func logStreamWrapError(gitspaceLogger orchestratorTypes.GitspaceLogger, msg string, err error) error { +func logStreamWrapError(gitspaceLogger gitspaceTypes.GitspaceLogger, msg string, err error) error { gitspaceLogger.Error(msg, err) return fmt.Errorf("%s: %w", msg, err) } @@ -58,7 +58,7 @@ func ManageContainer( action Action, containerName string, dockerClient *client.Client, - gitspaceLogger orchestratorTypes.GitspaceLogger, + gitspaceLogger gitspaceTypes.GitspaceLogger, ) error { var err error switch action { @@ -123,7 +123,7 @@ func CreateContainer( dockerClient *client.Client, imageName string, containerName string, - gitspaceLogger orchestratorTypes.GitspaceLogger, + gitspaceLogger gitspaceTypes.GitspaceLogger, bindMountSource string, bindMountTarget string, mountType mount.Type, @@ -225,7 +225,7 @@ func PullImage( ctx context.Context, imageName string, dockerClient *client.Client, - gitspaceLogger orchestratorTypes.GitspaceLogger, + gitspaceLogger gitspaceTypes.GitspaceLogger, ) error { gitspaceLogger.Info("Pulling image: " + imageName) diff --git a/app/gitspace/orchestrator/container/devcontainer_steps.go b/app/gitspace/orchestrator/container/devcontainer_steps.go index 8a2b3e4b8..7db9ee483 100644 --- a/app/gitspace/orchestrator/container/devcontainer_steps.go +++ b/app/gitspace/orchestrator/container/devcontainer_steps.go @@ -23,9 +23,9 @@ import ( "github.com/harness/gitness/app/gitspace/orchestrator/devcontainer" "github.com/harness/gitness/app/gitspace/orchestrator/git" "github.com/harness/gitness/app/gitspace/orchestrator/ide" - orchestratorTypes "github.com/harness/gitness/app/gitspace/orchestrator/types" "github.com/harness/gitness/app/gitspace/orchestrator/user" "github.com/harness/gitness/app/gitspace/scm" + gitspaceTypes "github.com/harness/gitness/app/gitspace/types" "github.com/harness/gitness/types" "github.com/harness/gitness/types/enum" ) @@ -34,11 +34,12 @@ import ( func (e *EmbeddedDockerOrchestrator) setupGitspaceAndIDE( ctx context.Context, exec *devcontainer.Exec, - gitspaceLogger orchestratorTypes.GitspaceLogger, + gitspaceLogger gitspaceTypes.GitspaceLogger, ideService ide.IDE, gitspaceConfig types.GitspaceConfig, resolvedRepoDetails scm.ResolvedDetails, defaultBaseImage string, + environment []string, ) error { homeDir := GetUserHomeDir(gitspaceConfig.GitspaceUser.Identifier) devcontainerConfig := resolvedRepoDetails.DevcontainerConfig @@ -47,45 +48,49 @@ func (e *EmbeddedDockerOrchestrator) setupGitspaceAndIDE( // Register setup steps e.RegisterStep("Validate Supported OS", ValidateSupportedOS, true) e.RegisterStep("Manage User", - func(ctx context.Context, exec *devcontainer.Exec, gitspaceLogger orchestratorTypes.GitspaceLogger) error { + func(ctx context.Context, exec *devcontainer.Exec, gitspaceLogger gitspaceTypes.GitspaceLogger) error { return ManageUser(ctx, exec, e.userService, gitspaceLogger) }, true) + e.RegisterStep("Set environment", + func(ctx context.Context, exec *devcontainer.Exec, gitspaceLogger gitspaceTypes.GitspaceLogger) error { + return SetEnv(ctx, exec, gitspaceLogger, environment) + }, true) e.RegisterStep("Install Tools", - func(ctx context.Context, exec *devcontainer.Exec, gitspaceLogger orchestratorTypes.GitspaceLogger) error { + func(ctx context.Context, exec *devcontainer.Exec, gitspaceLogger gitspaceTypes.GitspaceLogger) error { return InstallTools(ctx, exec, gitspaceLogger, gitspaceConfig.IDE) }, true) e.RegisterStep("Setup IDE", - func(ctx context.Context, exec *devcontainer.Exec, gitspaceLogger orchestratorTypes.GitspaceLogger) error { + func(ctx context.Context, exec *devcontainer.Exec, gitspaceLogger gitspaceTypes.GitspaceLogger) error { return SetupIDE(ctx, exec, ideService, gitspaceLogger) }, true) e.RegisterStep("Run IDE", - func(ctx context.Context, exec *devcontainer.Exec, gitspaceLogger orchestratorTypes.GitspaceLogger) error { + func(ctx context.Context, exec *devcontainer.Exec, gitspaceLogger gitspaceTypes.GitspaceLogger) error { return RunIDE(ctx, exec, ideService, gitspaceLogger) }, true) e.RegisterStep("Install Git", - func(ctx context.Context, exec *devcontainer.Exec, gitspaceLogger orchestratorTypes.GitspaceLogger) error { + func(ctx context.Context, exec *devcontainer.Exec, gitspaceLogger gitspaceTypes.GitspaceLogger) error { return InstallGit(ctx, exec, e.gitService, gitspaceLogger) }, true) e.RegisterStep("Setup Git Credentials", - func(ctx context.Context, exec *devcontainer.Exec, gitspaceLogger orchestratorTypes.GitspaceLogger) error { + func(ctx context.Context, exec *devcontainer.Exec, gitspaceLogger gitspaceTypes.GitspaceLogger) error { if resolvedRepoDetails.ResolvedCredentials.Credentials != nil { return SetupGitCredentials(ctx, exec, resolvedRepoDetails, e.gitService, gitspaceLogger) } return nil }, true) e.RegisterStep("Clone Code", - func(ctx context.Context, exec *devcontainer.Exec, gitspaceLogger orchestratorTypes.GitspaceLogger) error { + func(ctx context.Context, exec *devcontainer.Exec, gitspaceLogger gitspaceTypes.GitspaceLogger) error { return CloneCode(ctx, exec, defaultBaseImage, resolvedRepoDetails, e.gitService, gitspaceLogger) }, true) // Register the Execute Command steps (PostCreate and PostStart) e.RegisterStep("Execute PostCreate Command", - func(ctx context.Context, exec *devcontainer.Exec, gitspaceLogger orchestratorTypes.GitspaceLogger) error { + func(ctx context.Context, exec *devcontainer.Exec, gitspaceLogger gitspaceTypes.GitspaceLogger) error { command := ExtractCommand(PostCreateAction, devcontainerConfig) return ExecuteCommand(ctx, exec, codeRepoDir, gitspaceLogger, command, PostCreateAction) }, false) e.RegisterStep("Execute PostStart Command", - func(ctx context.Context, exec *devcontainer.Exec, gitspaceLogger orchestratorTypes.GitspaceLogger) error { + func(ctx context.Context, exec *devcontainer.Exec, gitspaceLogger gitspaceTypes.GitspaceLogger) error { command := ExtractCommand(PostStartAction, devcontainerConfig) return ExecuteCommand(ctx, exec, codeRepoDir, gitspaceLogger, command, PostStartAction) }, false) @@ -100,27 +105,25 @@ func (e *EmbeddedDockerOrchestrator) setupGitspaceAndIDE( func InstallTools( ctx context.Context, exec *devcontainer.Exec, - gitspaceLogger orchestratorTypes.GitspaceLogger, + gitspaceLogger gitspaceTypes.GitspaceLogger, ideType enum.IDEType, ) error { - output, err := common.InstallTools(ctx, exec, ideType) + err := common.InstallTools(ctx, exec, ideType, gitspaceLogger) if err != nil { return logStreamWrapError(gitspaceLogger, "Error while installing tools inside container", err) } - gitspaceLogger.Info("Tools installation output...\n" + string(output)) return nil } func ValidateSupportedOS( ctx context.Context, exec *devcontainer.Exec, - gitspaceLogger orchestratorTypes.GitspaceLogger, + gitspaceLogger gitspaceTypes.GitspaceLogger, ) error { - output, err := common.ValidateSupportedOS(ctx, exec) + err := common.ValidateSupportedOS(ctx, exec, gitspaceLogger) if err != nil { return logStreamWrapError(gitspaceLogger, "Error while detecting OS inside container", err) } - gitspaceLogger.Info("Validate supported OSes...\n" + string(output)) return nil } @@ -128,7 +131,7 @@ func ExecuteCommand( ctx context.Context, exec *devcontainer.Exec, codeRepoDir string, - gitspaceLogger orchestratorTypes.GitspaceLogger, + gitspaceLogger gitspaceTypes.GitspaceLogger, command string, actionType PostAction, ) error { @@ -137,12 +140,17 @@ func ExecuteCommand( return nil } gitspaceLogger.Info(fmt.Sprintf("Executing %s command: %s", actionType, command)) - output, err := exec.ExecuteCommand(ctx, command, true, false, codeRepoDir) + gitspaceLogger.Info(fmt.Sprintf("%s command execution output...", actionType)) + // Create a channel to stream command output + outputCh := make(chan []byte) + err := exec.ExecuteCommand(ctx, command, true, false, codeRepoDir, outputCh) if err != nil { return logStreamWrapError( gitspaceLogger, fmt.Sprintf("Error while executing %s command", actionType), err) } - gitspaceLogger.Info(fmt.Sprintf("%s command execution output...\n %s", actionType, string(output))) + for output := range outputCh { + gitspaceLogger.Info(string(output)) + } gitspaceLogger.Info(fmt.Sprintf("Successfully executed %s command", actionType)) return nil } @@ -153,13 +161,12 @@ func CloneCode( defaultBaseImage string, resolvedRepoDetails scm.ResolvedDetails, gitService git.Service, - gitspaceLogger orchestratorTypes.GitspaceLogger, + gitspaceLogger gitspaceTypes.GitspaceLogger, ) error { - output, err := gitService.CloneCode(ctx, exec, resolvedRepoDetails, defaultBaseImage) + err := gitService.CloneCode(ctx, exec, resolvedRepoDetails, defaultBaseImage, gitspaceLogger) if err != nil { return logStreamWrapError(gitspaceLogger, "Error while cloning code inside container", err) } - gitspaceLogger.Info("Clone output...\n" + string(output)) return nil } @@ -167,14 +174,12 @@ func InstallGit( ctx context.Context, exec *devcontainer.Exec, gitService git.Service, - gitspaceLogger orchestratorTypes.GitspaceLogger, + gitspaceLogger gitspaceTypes.GitspaceLogger, ) error { - output, err := gitService.Install(ctx, exec) + err := gitService.Install(ctx, exec, gitspaceLogger) if err != nil { return logStreamWrapError(gitspaceLogger, "Error while installing git inside container", err) } - - gitspaceLogger.Info("Install git output...\n" + string(output)) return nil } @@ -183,15 +188,13 @@ func SetupGitCredentials( exec *devcontainer.Exec, resolvedRepoDetails scm.ResolvedDetails, gitService git.Service, - gitspaceLogger orchestratorTypes.GitspaceLogger, + gitspaceLogger gitspaceTypes.GitspaceLogger, ) error { - output, err := gitService.SetupCredentials(ctx, exec, resolvedRepoDetails) + err := gitService.SetupCredentials(ctx, exec, resolvedRepoDetails, gitspaceLogger) if err != nil { return logStreamWrapError( gitspaceLogger, "Error while setting up git credentials inside container", err) } - - gitspaceLogger.Info("Setting up git credentials output...\n" + string(output)) return nil } @@ -199,13 +202,12 @@ func ManageUser( ctx context.Context, exec *devcontainer.Exec, userService user.Service, - gitspaceLogger orchestratorTypes.GitspaceLogger, + gitspaceLogger gitspaceTypes.GitspaceLogger, ) error { - output, err := userService.Manage(ctx, exec) + err := userService.Manage(ctx, exec, gitspaceLogger) if err != nil { return logStreamWrapError(gitspaceLogger, "Error while creating user inside container", err) } - gitspaceLogger.Info("Managing user output...\n" + string(output)) return nil } @@ -213,17 +215,13 @@ func SetupIDE( ctx context.Context, exec *devcontainer.Exec, ideService ide.IDE, - gitspaceLogger orchestratorTypes.GitspaceLogger, + gitspaceLogger gitspaceTypes.GitspaceLogger, ) error { gitspaceLogger.Info("Setting up IDE inside container: " + string(ideService.Type())) - - output, err := ideService.Setup(ctx, exec) + err := ideService.Setup(ctx, exec, gitspaceLogger) if err != nil { return logStreamWrapError(gitspaceLogger, "Error while setting up IDE inside container", err) } - - gitspaceLogger.Info("IDE setup output...\n" + string(output)) - gitspaceLogger.Info("Successfully set up IDE inside container") return nil } @@ -231,15 +229,27 @@ func RunIDE( ctx context.Context, exec *devcontainer.Exec, ideService ide.IDE, - gitspaceLogger orchestratorTypes.GitspaceLogger, + gitspaceLogger gitspaceTypes.GitspaceLogger, ) error { gitspaceLogger.Info("Running the IDE inside container: " + string(ideService.Type())) - output, err := ideService.Run(ctx, exec) + err := ideService.Run(ctx, exec, gitspaceLogger) if err != nil { return logStreamWrapError(gitspaceLogger, "Error while running IDE inside container", err) } - - gitspaceLogger.Info("IDE run output...\n" + string(output)) - gitspaceLogger.Info("Successfully run the IDE inside container") + return nil +} + +func SetEnv( + ctx context.Context, + exec *devcontainer.Exec, + gitspaceLogger gitspaceTypes.GitspaceLogger, + environment []string, +) error { + if len(environment) > 0 { + err := common.SetEnv(ctx, exec, gitspaceLogger, environment) + if err != nil { + return logStreamWrapError(gitspaceLogger, "Error while installing tools inside container", err) + } + } return nil } diff --git a/app/gitspace/orchestrator/container/embedded_docker_provider.go b/app/gitspace/orchestrator/container/embedded_docker_provider.go index ed3fcc70e..8acef4c5c 100644 --- a/app/gitspace/orchestrator/container/embedded_docker_provider.go +++ b/app/gitspace/orchestrator/container/embedded_docker_provider.go @@ -23,9 +23,9 @@ import ( "github.com/harness/gitness/app/gitspace/orchestrator/devcontainer" "github.com/harness/gitness/app/gitspace/orchestrator/git" "github.com/harness/gitness/app/gitspace/orchestrator/ide" - orchestratorTypes "github.com/harness/gitness/app/gitspace/orchestrator/types" "github.com/harness/gitness/app/gitspace/orchestrator/user" "github.com/harness/gitness/app/gitspace/scm" + gitspaceTypes "github.com/harness/gitness/app/gitspace/types" "github.com/harness/gitness/infraprovider" "github.com/harness/gitness/types" @@ -41,7 +41,7 @@ const ( ) type EmbeddedDockerOrchestrator struct { - steps []orchestratorTypes.Step // Steps registry + steps []gitspaceTypes.Step // Steps registry dockerClientFactory *infraprovider.DockerClientFactory statefulLogger *logutil.StatefulLogger gitService git.Service @@ -51,10 +51,10 @@ type EmbeddedDockerOrchestrator struct { // RegisterStep registers a new setup step with an option to stop or continue on failure. func (e *EmbeddedDockerOrchestrator) RegisterStep( name string, - execute func(ctx context.Context, exec *devcontainer.Exec, gitspaceLogger orchestratorTypes.GitspaceLogger) error, + execute func(ctx context.Context, exec *devcontainer.Exec, logger gitspaceTypes.GitspaceLogger) error, stopOnFailure bool, ) { - step := orchestratorTypes.Step{ + step := gitspaceTypes.Step{ Name: name, Execute: execute, StopOnFailure: stopOnFailure, @@ -66,7 +66,7 @@ func (e *EmbeddedDockerOrchestrator) RegisterStep( func (e *EmbeddedDockerOrchestrator) ExecuteSteps( ctx context.Context, exec *devcontainer.Exec, - gitspaceLogger orchestratorTypes.GitspaceLogger, + gitspaceLogger gitspaceTypes.GitspaceLogger, ) error { for _, step := range e.steps { // Execute the step @@ -378,7 +378,7 @@ func (e *EmbeddedDockerOrchestrator) runGitspaceSetupSteps( infrastructure types.Infrastructure, resolvedRepoDetails scm.ResolvedDetails, defaultBaseImage string, - gitspaceLogger orchestratorTypes.GitspaceLogger, + gitspaceLogger gitspaceTypes.GitspaceLogger, ) error { homeDir := GetUserHomeDir(gitspaceConfig.GitspaceUser.Identifier) containerName := GetGitspaceContainerName(gitspaceConfig) @@ -450,6 +450,7 @@ func (e *EmbeddedDockerOrchestrator) runGitspaceSetupSteps( gitspaceConfig, resolvedRepoDetails, defaultBaseImage, + environment, ); err != nil { return err } diff --git a/app/gitspace/orchestrator/devcontainer/exec.go b/app/gitspace/orchestrator/devcontainer/exec.go index 3a034061a..5f98e1048 100644 --- a/app/gitspace/orchestrator/devcontainer/exec.go +++ b/app/gitspace/orchestrator/devcontainer/exec.go @@ -15,10 +15,13 @@ package devcontainer import ( - "bytes" + "bufio" "context" "fmt" "io" + "log" + "strings" + "sync" "github.com/harness/gitness/types/enum" @@ -28,6 +31,7 @@ import ( ) const RootUser = "root" +const ErrMsgTCP = "unable to upgrade to tcp, received 200" type Exec struct { ContainerName string @@ -39,8 +43,8 @@ type Exec struct { } type execResult struct { - StdOut []byte - StdErr []byte + StdOut io.Reader + StdErr io.Reader ExitCode int } @@ -50,49 +54,48 @@ func (e *Exec) ExecuteCommand( root bool, detach bool, workingDir string, -) ([]byte, error) { + outputCh chan []byte, // channel to stream output as []byte +) error { user := e.UserIdentifier if root { user = RootUser } cmd := []string{"/bin/sh", "-c", command} - execConfig := container.ExecOptions{ User: user, - AttachStdout: true, - AttachStderr: true, + AttachStdout: !detach, + AttachStderr: !detach, Cmd: cmd, Detach: detach, WorkingDir: workingDir, } - execID, err := e.DockerClient.ContainerExecCreate(ctx, e.ContainerName, execConfig) + // Create exec instance for the container + containerExecCreate, err := e.DockerClient.ContainerExecCreate(ctx, e.ContainerName, execConfig) if err != nil { - return nil, fmt.Errorf("failed to create docker exec for container %s: %w", e.ContainerName, err) + return fmt.Errorf("failed to create docker exec for container %s: %w", e.ContainerName, err) } - resp, err := e.attachAndInspectExec(ctx, execID.ID, detach) - if err != nil && err.Error() != "unable to upgrade to tcp, received 200" { - return nil, fmt.Errorf("failed to start docker exec for container %s: %w", e.ContainerName, err) + // Attach and inspect exec session to get the output + inspectExec, err := e.attachAndInspectExec(ctx, containerExecCreate.ID, detach) + if err != nil && !strings.Contains(err.Error(), ErrMsgTCP) { + return fmt.Errorf("failed to start docker exec for container %s: %w", e.ContainerName, err) + } + // If in detach mode, exit early as the command will run in the background + if detach { + close(outputCh) + return nil } - if resp != nil && resp.ExitCode != 0 { - var errLog string - if resp.StdErr != nil { - errLog = string(resp.StdErr) - } - - return nil, fmt.Errorf("error during command execution in container %s. exit code %d. log: %s", - e.ContainerName, resp.ExitCode, errLog) + // Wait for the exit code after the command completes + if inspectExec != nil && inspectExec.ExitCode != 0 { + return fmt.Errorf("error during command execution in container %s. exit code %d", + e.ContainerName, inspectExec.ExitCode) } - var stdOutput []byte - if resp != nil { - stdOutput = resp.StdOut - } - - return stdOutput, nil + e.streamResponse(inspectExec, outputCh) + return nil } func (e *Exec) ExecuteCommandInHomeDirectory( @@ -100,55 +103,91 @@ func (e *Exec) ExecuteCommandInHomeDirectory( command string, root bool, detach bool, -) ([]byte, error) { - return e.ExecuteCommand(ctx, command, root, detach, e.HomeDir) + outputCh chan []byte, // channel to stream output as []byte +) error { + return e.ExecuteCommand(ctx, command, root, detach, e.HomeDir, outputCh) } func (e *Exec) attachAndInspectExec(ctx context.Context, id string, detach bool) (*execResult, error) { resp, attachErr := e.DockerClient.ContainerExecAttach(ctx, id, container.ExecStartOptions{Detach: detach}) if attachErr != nil { - return nil, attachErr - } - defer resp.Close() - - var outBuf, errBuf bytes.Buffer - copyErr := make(chan error) - - go func() { - // StdCopy demultiplexes the stream into two buffers - _, err := stdcopy.StdCopy(&outBuf, &errBuf, resp.Reader) - copyErr <- err - }() - - select { - case err := <-copyErr: - if err != nil { - return nil, err - } - break - - case <-ctx.Done(): - return nil, ctx.Err() + return nil, fmt.Errorf("failed to attach to exec session: %w", attachErr) } - stdout, err := io.ReadAll(&outBuf) - if err != nil { - return nil, fmt.Errorf("failed to read stdout of exec for container %s: %w", e.ContainerName, err) + // If in detach mode, we just need to close the connection, not process output + if detach { + // No need to process output in detach mode, so we simply close the connection + resp.Close() + return nil, nil //nolint:nilnil } - stderr, err := io.ReadAll(&errBuf) - if err != nil { - return nil, fmt.Errorf("failed to read stderr of exec for container %s: %w", e.ContainerName, err) - } + // Create pipes for stdout and stderr + stdoutPipe, stdoutWriter := io.Pipe() + stderrPipe, stderrWriter := io.Pipe() - inspectRes, err := e.DockerClient.ContainerExecInspect(ctx, id) - if err != nil { - return nil, fmt.Errorf("failed to inspect exec for container %s: %w", e.ContainerName, err) - } + go e.copyOutput(resp.Reader, stdoutWriter, stderrWriter) + // Return the output streams and the response return &execResult{ - StdOut: stdout, - StdErr: stderr, - ExitCode: inspectRes.ExitCode, + StdOut: stdoutPipe, // Pipe for stdout + StdErr: stderrPipe, // Pipe for stderr }, nil } + +func (e *Exec) streamResponse(resp *execResult, outputCh chan []byte) { + // Stream the output asynchronously if not in detach mode + go func() { + if resp != nil { + var wg sync.WaitGroup + + // Handle stdout as a streaming reader + if resp.StdOut != nil { + wg.Add(1) + go e.streamStdOut(resp.StdOut, outputCh, &wg) + } + // Handle stderr as a streaming reader + if resp.StdErr != nil { + wg.Add(1) + go e.streamStdErr(resp.StdErr, outputCh, &wg) + } + // Wait for all readers to finish before closing the channel + wg.Wait() + // Close the output channel after all output has been processed + close(outputCh) + } + }() +} + +// copyOutput copies the output from the exec response to the pipes, and is blocking. +func (e *Exec) copyOutput(reader io.Reader, stdoutWriter, stderrWriter io.WriteCloser) { + _, err := stdcopy.StdCopy(stdoutWriter, stderrWriter, reader) + if err != nil { + log.Printf("Error copying output: %v", err) + } + stdoutWriter.Close() + stderrWriter.Close() +} + +// streamStdOut reads from the stdout pipe and sends each line to the output channel. +func (e *Exec) streamStdOut(stdout io.Reader, outputCh chan []byte, wg *sync.WaitGroup) { + defer wg.Done() + stdoutReader := bufio.NewScanner(stdout) + for stdoutReader.Scan() { + outputCh <- stdoutReader.Bytes() + } + if err := stdoutReader.Err(); err != nil { + log.Println("Error reading stdout:", err) + } +} + +// streamStdErr reads from the stderr pipe and sends each line to the output channel. +func (e *Exec) streamStdErr(stderr io.Reader, outputCh chan []byte, wg *sync.WaitGroup) { + defer wg.Done() + stderrReader := bufio.NewScanner(stderr) + for stderrReader.Scan() { + outputCh <- []byte("ERR> " + stderrReader.Text()) + } + if err := stderrReader.Err(); err != nil { + log.Println("Error reading stderr:", err) + } +} diff --git a/app/gitspace/orchestrator/git/git.go b/app/gitspace/orchestrator/git/git.go index 25b9d4233..e9ea33a55 100644 --- a/app/gitspace/orchestrator/git/git.go +++ b/app/gitspace/orchestrator/git/git.go @@ -19,18 +19,23 @@ import ( "github.com/harness/gitness/app/gitspace/orchestrator/devcontainer" "github.com/harness/gitness/app/gitspace/scm" + "github.com/harness/gitness/app/gitspace/types" ) type Service interface { // Install ensures git is installed in the container. - Install(ctx context.Context, exec *devcontainer.Exec) ([]byte, error) + Install(ctx context.Context, + exec *devcontainer.Exec, + gitspaceLogger types.GitspaceLogger, + ) error // SetupCredentials sets the user's git credentials inside the container. SetupCredentials( ctx context.Context, exec *devcontainer.Exec, resolvedRepoDetails scm.ResolvedDetails, - ) ([]byte, error) + gitspaceLogger types.GitspaceLogger, + ) error // CloneCode clones the code and ensures devcontainer file is present. CloneCode( @@ -38,5 +43,6 @@ type Service interface { exec *devcontainer.Exec, resolvedRepoDetails scm.ResolvedDetails, defaultBaseImage string, - ) ([]byte, error) + gitspaceLogger types.GitspaceLogger, + ) error } diff --git a/app/gitspace/orchestrator/git/git_impl.go b/app/gitspace/orchestrator/git/git_impl.go index bf61133ca..f3d4aca75 100644 --- a/app/gitspace/orchestrator/git/git_impl.go +++ b/app/gitspace/orchestrator/git/git_impl.go @@ -23,6 +23,7 @@ import ( "github.com/harness/gitness/app/gitspace/orchestrator/devcontainer" "github.com/harness/gitness/app/gitspace/orchestrator/template" "github.com/harness/gitness/app/gitspace/scm" + "github.com/harness/gitness/app/gitspace/types" ) var _ Service = (*ServiceImpl)(nil) @@ -38,50 +39,52 @@ func NewGitServiceImpl() Service { return &ServiceImpl{} } -func (g *ServiceImpl) Install(ctx context.Context, exec *devcontainer.Exec) ([]byte, error) { +func (g *ServiceImpl) Install( + ctx context.Context, + exec *devcontainer.Exec, + gitspaceLogger types.GitspaceLogger, +) error { script, err := template.GenerateScriptFromTemplate( templateGitInstallScript, &template.SetupGitInstallPayload{ OSInfoScript: common.GetOSInfoScript(), }) if err != nil { - return nil, fmt.Errorf( + return fmt.Errorf( "failed to generate scipt to setup git install from template %s: %w", templateGitInstallScript, err) } - output := "Setting up git inside container\n" - _, err = exec.ExecuteCommandInHomeDirectory(ctx, script, true, false) + gitspaceLogger.Info("Install git output...") + gitspaceLogger.Info("Setting up git inside container") + err = common.ExecuteCommandInHomeDirAndLog(ctx, exec, script, true, gitspaceLogger) if err != nil { - return nil, fmt.Errorf("failed to setup git: %w", err) + return fmt.Errorf("failed to setup git: %w", err) } + gitspaceLogger.Info("Successfully setup git") - output += "Successfully setup git\n" - - return []byte(output), nil + return nil } func (g *ServiceImpl) SetupCredentials( ctx context.Context, exec *devcontainer.Exec, resolvedRepoDetails scm.ResolvedDetails, -) ([]byte, error) { + gitspaceLogger types.GitspaceLogger, +) error { script, err := template.GenerateScriptFromTemplate( templateSetupGitCredentials, &template.SetupGitCredentialsPayload{ CloneURLWithCreds: resolvedRepoDetails.CloneURL, }) if err != nil { - return nil, fmt.Errorf( + return fmt.Errorf( "failed to generate scipt to setup git credentials from template %s: %w", templateSetupGitCredentials, err) } - - output := "Setting up git credentials inside container\n" - - _, err = exec.ExecuteCommandInHomeDirectory(ctx, script, false, false) + gitspaceLogger.Info("Setting up git credentials output...") + gitspaceLogger.Info("Setting up git credentials inside container") + err = common.ExecuteCommandInHomeDirAndLog(ctx, exec, script, false, gitspaceLogger) if err != nil { - return nil, fmt.Errorf("failed to setup git credentials: %w", err) + return fmt.Errorf("failed to setup git credentials: %w", err) } - - output += "Successfully setup git credentials\n" - - return []byte(output), nil + gitspaceLogger.Info("Successfully setup git credentials") + return nil } func (g *ServiceImpl) CloneCode( @@ -89,10 +92,11 @@ func (g *ServiceImpl) CloneCode( exec *devcontainer.Exec, resolvedRepoDetails scm.ResolvedDetails, defaultBaseImage string, -) ([]byte, error) { + gitspaceLogger types.GitspaceLogger, +) error { cloneURL, err := url.Parse(resolvedRepoDetails.CloneURL) if err != nil { - return nil, fmt.Errorf( + return fmt.Errorf( "failed to parse clone url %s: %w", resolvedRepoDetails.CloneURL, err) } cloneURL.User = nil @@ -109,18 +113,16 @@ func (g *ServiceImpl) CloneCode( script, err := template.GenerateScriptFromTemplate( templateCloneCode, data) if err != nil { - return nil, fmt.Errorf( + return fmt.Errorf( "failed to generate scipt to clone code from template %s: %w", templateCloneCode, err) } - - output := "Cloning code inside container\n" - - _, err = exec.ExecuteCommandInHomeDirectory(ctx, script, false, false) + gitspaceLogger.Info("Clone output...") + gitspaceLogger.Info("Cloning code inside container") + err = common.ExecuteCommandInHomeDirAndLog(ctx, exec, script, false, gitspaceLogger) if err != nil { - return nil, fmt.Errorf("failed to clone code: %w", err) + return fmt.Errorf("failed to clone code: %w", err) } + gitspaceLogger.Info("Successfully clone code") - output += "Successfully clone code\n" - - return []byte(output), nil + return nil } diff --git a/app/gitspace/orchestrator/ide/ide.go b/app/gitspace/orchestrator/ide/ide.go index 0e4b7fa35..2d17b59e3 100644 --- a/app/gitspace/orchestrator/ide/ide.go +++ b/app/gitspace/orchestrator/ide/ide.go @@ -18,6 +18,7 @@ import ( "context" "github.com/harness/gitness/app/gitspace/orchestrator/devcontainer" + gitspaceTypes "github.com/harness/gitness/app/gitspace/types" "github.com/harness/gitness/types" "github.com/harness/gitness/types/enum" ) @@ -25,10 +26,10 @@ import ( type IDE interface { // Setup is responsible for doing all the operations for setting up the IDE in the container e.g. installation, // copying settings and configurations. - Setup(ctx context.Context, exec *devcontainer.Exec) ([]byte, error) + Setup(ctx context.Context, exec *devcontainer.Exec, gitspaceLogger gitspaceTypes.GitspaceLogger) error // Run runs the IDE and supporting services. - Run(ctx context.Context, exec *devcontainer.Exec) ([]byte, error) + Run(ctx context.Context, exec *devcontainer.Exec, gitspaceLogger gitspaceTypes.GitspaceLogger) error // Port provides the port which will be used by this IDE. Port() *types.GitspacePort diff --git a/app/gitspace/orchestrator/ide/vscode.go b/app/gitspace/orchestrator/ide/vscode.go index 9bb3d7911..bec4e910f 100644 --- a/app/gitspace/orchestrator/ide/vscode.go +++ b/app/gitspace/orchestrator/ide/vscode.go @@ -22,6 +22,7 @@ import ( "github.com/harness/gitness/app/gitspace/orchestrator/common" "github.com/harness/gitness/app/gitspace/orchestrator/devcontainer" "github.com/harness/gitness/app/gitspace/orchestrator/template" + gitspaceTypes "github.com/harness/gitness/app/gitspace/types" "github.com/harness/gitness/types" "github.com/harness/gitness/types/enum" ) @@ -47,7 +48,8 @@ func NewVsCodeService(config *VSCodeConfig) *VSCode { func (v *VSCode) Setup( ctx context.Context, exec *devcontainer.Exec, -) ([]byte, error) { + gitspaceLogger gitspaceTypes.GitspaceLogger, +) error { osInfoScript := common.GetOSInfoScript() sshServerScript, err := template.GenerateScriptFromTemplate( templateSetupSSHServer, &template.SetupSSHServerPayload{ @@ -56,43 +58,43 @@ func (v *VSCode) Setup( OSInfoScript: osInfoScript, }) if err != nil { - return nil, fmt.Errorf( + return fmt.Errorf( "failed to generate scipt to setup ssh server from template %s: %w", templateSetupSSHServer, err) } - output := "Installing ssh-server inside container\n" - - _, err = exec.ExecuteCommandInHomeDirectory(ctx, sshServerScript, true, false) + gitspaceLogger.Info("Installing ssh-server inside container") + gitspaceLogger.Info("IDE setup output...") + err = common.ExecuteCommandInHomeDirAndLog(ctx, exec, sshServerScript, true, gitspaceLogger) if err != nil { - return nil, fmt.Errorf("failed to setup SSH serverr: %w", err) + return fmt.Errorf("failed to setup SSH serverr: %w", err) } - - output += "Successfully installed ssh-server\n" - - return []byte(output), nil + gitspaceLogger.Info("Successfully installed ssh-server") + gitspaceLogger.Info("Successfully set up IDE inside container") + return nil } // Run runs the SSH server inside the container. -func (v *VSCode) Run(ctx context.Context, exec *devcontainer.Exec) ([]byte, error) { - var output = "" - +func (v *VSCode) Run( + ctx context.Context, + exec *devcontainer.Exec, + gitspaceLogger gitspaceTypes.GitspaceLogger, +) error { runSSHScript, err := template.GenerateScriptFromTemplate( templateRunSSHServer, &template.RunSSHServerPayload{ Port: strconv.Itoa(v.config.Port), }) if err != nil { - return nil, fmt.Errorf( + return fmt.Errorf( "failed to generate scipt to run ssh server from template %s: %w", templateRunSSHServer, err) } - - execOutput, err := exec.ExecuteCommandInHomeDirectory(ctx, runSSHScript, true, false) + gitspaceLogger.Info("SSH server run output...") + err = common.ExecuteCommandInHomeDirAndLog(ctx, exec, runSSHScript, true, gitspaceLogger) if err != nil { - return nil, fmt.Errorf("failed to run SSH server: %w", err) + return fmt.Errorf("failed to run SSH server: %w", err) } + gitspaceLogger.Info("Successfully run ssh-server") - output += "SSH server run output...\n" + string(execOutput) + "\nSuccessfully run ssh-server\n" - - return []byte(output), nil + return nil } // Port returns the port on which the ssh-server is listening. diff --git a/app/gitspace/orchestrator/ide/vscodeweb.go b/app/gitspace/orchestrator/ide/vscodeweb.go index caf31af97..d6e1972d1 100644 --- a/app/gitspace/orchestrator/ide/vscodeweb.go +++ b/app/gitspace/orchestrator/ide/vscodeweb.go @@ -27,6 +27,7 @@ import ( "github.com/harness/gitness/app/gitspace/orchestrator/devcontainer" "github.com/harness/gitness/app/gitspace/orchestrator/template" + gitspaceTypes "github.com/harness/gitness/app/gitspace/types" "github.com/harness/gitness/types" "github.com/harness/gitness/types/enum" @@ -61,58 +62,75 @@ func NewVsCodeWebService(config *VSCodeWebConfig) *VSCodeWeb { } // Setup runs the installScript which downloads the required version of the code-server binary. -func (v *VSCodeWeb) Setup(ctx context.Context, exec *devcontainer.Exec) ([]byte, error) { - output := "Installing VSCode Web inside container.\n" - - _, err := exec.ExecuteCommandInHomeDirectory(ctx, installScript, true, false) +func (v *VSCodeWeb) Setup( + ctx context.Context, + exec *devcontainer.Exec, + gitspaceLogger gitspaceTypes.GitspaceLogger, +) error { + gitspaceLogger.Info("Installing VSCode Web inside container.") + gitspaceLogger.Info("IDE setup output...") + outputCh := make(chan []byte) + err := exec.ExecuteCommandInHomeDirectory(ctx, installScript, true, false, outputCh) if err != nil { - return nil, fmt.Errorf("failed to install VSCode Web: %w", err) + return fmt.Errorf("failed to install VSCode Web: %w", err) + } + for chunk := range outputCh { + _, err := io.Discard.Write(chunk) + if err != nil { + return err + } } - findOutput, err := exec.ExecuteCommandInHomeDirectory(ctx, findPathScript, true, false) - if err != nil { - return nil, fmt.Errorf("failed to find VSCode Web install path: %w", err) + findCh := make(chan []byte) + err = exec.ExecuteCommandInHomeDirectory(ctx, findPathScript, true, false, findCh) + var findOutput []byte + + for chunk := range findCh { + findOutput = append(findOutput, chunk...) // Concatenate each chunk of data } + if err != nil { + return fmt.Errorf("failed to find VSCode Web install path: %w", err) + } path := string(findOutput) startIndex := strings.Index(path, startMarker) endIndex := strings.Index(path, endMarker) if startIndex == -1 || endIndex == -1 || startIndex >= endIndex { - return nil, fmt.Errorf("could not find media folder path from find output: %s", path) + return fmt.Errorf("could not find media folder path from find output: %s", path) } mediaFolderPath := path[startIndex+len(startMarker) : endIndex] err = v.copyMediaToContainer(ctx, exec, mediaFolderPath) if err != nil { - return nil, fmt.Errorf("failed to copy media folder to container at path %s: %w", mediaFolderPath, err) + return fmt.Errorf("failed to copy media folder to container at path %s: %w", mediaFolderPath, err) } - - output += "Successfully installed VSCode Web inside container.\n" - - return []byte(output), nil + gitspaceLogger.Info("Successfully set up IDE inside container") + return nil } // Run runs the code-server binary. -func (v *VSCodeWeb) Run(ctx context.Context, exec *devcontainer.Exec) ([]byte, error) { - var output []byte - +func (v *VSCodeWeb) Run( + ctx context.Context, + exec *devcontainer.Exec, + gitspaceLogger gitspaceTypes.GitspaceLogger) error { runScript, err := template.GenerateScriptFromTemplate( templateRunVSCodeWeb, &template.RunVSCodeWebPayload{ Port: strconv.Itoa(v.config.Port), }) if err != nil { - return nil, fmt.Errorf( + return fmt.Errorf( "failed to generate scipt to run VSCode Web from template %s: %w", templateRunVSCodeWeb, err, ) } - - _, err = exec.ExecuteCommandInHomeDirectory(ctx, runScript, false, true) + gitspaceLogger.Info("Starting IDE ...") + outputCh := make(chan []byte) + err = exec.ExecuteCommandInHomeDirectory(ctx, runScript, false, false, outputCh) if err != nil { - return nil, fmt.Errorf("failed to run VSCode Web: %w", err) + return fmt.Errorf("failed to run VSCode Web: %w", err) } - return output, nil + return nil } // PortAndProtocol returns the port on which the code-server is listening. diff --git a/app/gitspace/orchestrator/template/template.go b/app/gitspace/orchestrator/template/template.go index ae7aa1b7b..d6d5477be 100644 --- a/app/gitspace/orchestrator/template/template.go +++ b/app/gitspace/orchestrator/template/template.go @@ -82,6 +82,10 @@ type SupportedOSDistributionPayload struct { OSInfoScript string } +type SetEnvPayload struct { + EnvVariables string +} + func init() { err := LoadTemplates() if err != nil { diff --git a/app/gitspace/orchestrator/template/templates/set_env.sh b/app/gitspace/orchestrator/template/templates/set_env.sh new file mode 100644 index 000000000..078add14a --- /dev/null +++ b/app/gitspace/orchestrator/template/templates/set_env.sh @@ -0,0 +1,29 @@ +#!/bin/sh +VARIABLES={{ .EnvVariables }} +# Check if the script is run as root, as modifying /etc/profile requires root privileges +if [[ $EUID -ne 0 ]]; then + echo "This script must be run as root." + exit 1 +fi +# Path to /etc/profile +PROFILE_FILE="/etc/profile" +# Process each line in the VARIABLES string +echo "$VARIABLES" | while IFS= read -r line; do + # Skip empty lines + [[ -z "$line" ]] && continue + + # Extract the variable name and value + var_name="${line%%=*}" # Part before '=' + var_value="${line#*=}" # Part after '=' + + # Create the export statement + export_statement="export $var_name=$var_value" + + # Check if the variable is already present in /etc/profile + if ! grep -q "^export $var_name=" "$PROFILE_FILE"; then + echo "$export_statement" >> "$PROFILE_FILE" + echo "Added $export_statement to $PROFILE_FILE" + else + echo "$var_name is already present in $PROFILE_FILE" + fi +done \ No newline at end of file diff --git a/app/gitspace/orchestrator/user/user.go b/app/gitspace/orchestrator/user/user.go index e9cfc11ed..516861f2f 100644 --- a/app/gitspace/orchestrator/user/user.go +++ b/app/gitspace/orchestrator/user/user.go @@ -18,9 +18,10 @@ import ( "context" "github.com/harness/gitness/app/gitspace/orchestrator/devcontainer" + "github.com/harness/gitness/app/gitspace/types" ) type Service interface { // Manage manager the linux user in the container. - Manage(ctx context.Context, exec *devcontainer.Exec) ([]byte, error) + Manage(ctx context.Context, exec *devcontainer.Exec, gitspaceLogger types.GitspaceLogger) error } diff --git a/app/gitspace/orchestrator/user/user_impl.go b/app/gitspace/orchestrator/user/user_impl.go index cc02f7c05..5bf75332b 100644 --- a/app/gitspace/orchestrator/user/user_impl.go +++ b/app/gitspace/orchestrator/user/user_impl.go @@ -21,6 +21,7 @@ import ( "github.com/harness/gitness/app/gitspace/orchestrator/common" "github.com/harness/gitness/app/gitspace/orchestrator/devcontainer" "github.com/harness/gitness/app/gitspace/orchestrator/template" + gitspaceTypes "github.com/harness/gitness/app/gitspace/types" ) var _ Service = (*ServiceImpl)(nil) @@ -34,7 +35,11 @@ func NewUserServiceImpl() Service { return &ServiceImpl{} } -func (u *ServiceImpl) Manage(ctx context.Context, exec *devcontainer.Exec) ([]byte, error) { +func (u *ServiceImpl) Manage( + ctx context.Context, + exec *devcontainer.Exec, + gitspaceLogger gitspaceTypes.GitspaceLogger, +) error { osInfoScript := common.GetOSInfoScript() script, err := template.GenerateScriptFromTemplate( templateManagerUser, &template.SetupUserPayload{ @@ -45,17 +50,18 @@ func (u *ServiceImpl) Manage(ctx context.Context, exec *devcontainer.Exec) ([]by OSInfoScript: osInfoScript, }) if err != nil { - return nil, fmt.Errorf( + return fmt.Errorf( "failed to generate scipt to manager user from template %s: %w", templateManagerUser, err) } - output := "Setting up user inside container\n" - _, err = exec.ExecuteCommandInHomeDirectory(ctx, script, true, false) + gitspaceLogger.Info("Setting up user inside container") + gitspaceLogger.Info("Managing user output...") + err = common.ExecuteCommandInHomeDirAndLog(ctx, exec, script, true, gitspaceLogger) if err != nil { - return nil, fmt.Errorf("failed to setup user: %w", err) + return fmt.Errorf("failed to setup user: %w", err) } - output += "Successfully setup user\n" + gitspaceLogger.Info("Successfully setup user") - return []byte(output), nil + return nil } diff --git a/app/gitspace/orchestrator/types/types.go b/app/gitspace/types/types.go similarity index 100% rename from app/gitspace/orchestrator/types/types.go rename to app/gitspace/types/types.go diff --git a/app/gitspace/orchestrator/types/zerolog_adapter.go b/app/gitspace/types/zerolog_adapter.go similarity index 96% rename from app/gitspace/orchestrator/types/zerolog_adapter.go rename to app/gitspace/types/zerolog_adapter.go index c0eb8ea71..cfc4fbb36 100644 --- a/app/gitspace/orchestrator/types/zerolog_adapter.go +++ b/app/gitspace/types/zerolog_adapter.go @@ -14,7 +14,9 @@ package types -import "github.com/rs/zerolog" +import ( + "github.com/rs/zerolog" +) // NewZerologAdapter creates a new adapter from a zerolog.Logger. func NewZerologAdapter(logger *zerolog.Logger) *ZerologAdapter {